View Javadoc

1   /*
2    * Copyright 2008-2010 smartics, Kronseder & Reiner GmbH
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *     http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  
17  package de.smartics.maven.issues.bugzilla;
18  
19  import java.io.IOException;
20  import java.io.ObjectInputStream;
21  import java.io.Serializable;
22  import java.util.List;
23  
24  import org.apache.commons.lang.ObjectUtils;
25  import org.apache.maven.artifact.versioning.ArtifactVersion;
26  import org.apache.maven.artifact.versioning.InvalidVersionSpecificationException;
27  import org.apache.maven.artifact.versioning.Restriction;
28  import org.apache.maven.artifact.versioning.VersionRange;
29  import org.eclipse.mylyn.internal.bugzilla.core.BugzillaAttribute;
30  
31  import de.smartics.maven.issues.ArtifactVersionRange;
32  
33  /**
34   * Implements a version range.
35   *
36   * @author <a href="mailto:robert.reiner@smartics.de">Robert Reiner</a>
37   * @version $Revision:591 $
38   */
39  public class BugzillaVersionRange implements Serializable, ArtifactVersionRange
40  {
41    // ********************************* Fields *********************************
42  
43    // --- constants ------------------------------------------------------------
44  
45    /**
46     * The class version identifier.
47     * <p>
48     * The value of this constant is {@value}.
49     */
50    private static final long serialVersionUID = 1L;
51  
52    /**
53     * The name of the parameter to specify the value for the Bugzilla query.
54     * <p>
55     * The value of this constant is {@value}.
56     * </p>
57     */
58    private static final String VALUE_PARAM_NAME = "value";
59  
60    /**
61     * The name of the parameter to specify the type for the Bugzilla query.
62     * <p>
63     * The value of this constant is {@value}.
64     * </p>
65     */
66    private static final String TYPE_PARAM_NAME = "type";
67  
68    /**
69     * The name of the parameter to specify the field for the Bugzilla query.
70     * <p>
71     * The value of this constant is {@value}.
72     * </p>
73     */
74    private static final String FIELD_PARAM_NAME = "field";
75  
76    // --- members --------------------------------------------------------------
77  
78    /**
79     * The identifier of the version to create a query part.
80     */
81    private final String versionId;
82  
83    /**
84     * The specification defining the range of valid versions.
85     */
86    private final String versionSpecification;
87  
88    /**
89     * The version range instance calculated by the version specification.
90     */
91    private transient VersionRange range;
92  
93    /**
94     * The start index to use to construct the field properties of the query to
95     * Bugzilla.
96     */
97    private final int fieldStartIndex;
98  
99    // ****************************** Initializer *******************************
100 
101   // ****************************** Constructors ******************************
102 
103   /**
104    * Convenience constructor supporting milestone versions.
105    *
106    * @param versionSpecification the specification defining the range of valid
107    *          versions.
108    * @throws InvalidVersionSpecificationException if the given
109    *           <code>versionSpecification</code> cannot be parsed.
110    */
111   public BugzillaVersionRange(final String versionSpecification)
112     throws InvalidVersionSpecificationException
113   {
114     this(BugzillaAttribute.TARGET_MILESTONE.getKey(), versionSpecification);
115   }
116 
117   /**
118    * Default constructor using start index for field query properties of zero.
119    *
120    * @param versionId the identifier of the version to create a query part.
121    * @param versionSpecification the specification defining the range of valid
122    *          versions.
123    * @throws InvalidVersionSpecificationException if the given
124    *           <code>versionSpecification</code> cannot be parsed.
125    */
126   public BugzillaVersionRange(final String versionId,
127       final String versionSpecification)
128     throws InvalidVersionSpecificationException
129   {
130     this(versionId, versionSpecification, 0);
131   }
132 
133   /**
134    * Constructor to specify the start index.
135    *
136    * @param versionId the identifier of the version to create a query part.
137    * @param versionSpecification the specification defining the range of valid
138    *          versions.
139    * @param fieldStartIndex the start index to use to construct the field
140    *          properties of the query to Bugzilla.
141    * @throws InvalidVersionSpecificationException if the given
142    *           <code>versionSpecification</code> cannot be parsed.
143    */
144   public BugzillaVersionRange(final String versionId,
145       final String versionSpecification, final int fieldStartIndex)
146     throws InvalidVersionSpecificationException
147   {
148     this.versionId = versionId;
149     this.versionSpecification = versionSpecification;
150     this.fieldStartIndex = fieldStartIndex;
151     this.range = VersionRange.createFromVersionSpec(versionSpecification);
152   }
153 
154   // ****************************** Inner Classes *****************************
155 
156   // ********************************* Methods ********************************
157 
158   // --- init -----------------------------------------------------------------
159 
160   // --- get&set --------------------------------------------------------------
161 
162   /**
163    * Returns the identifier of the version to create a query part.
164    *
165    * @return the identifier of the version to create a query part.
166    */
167   public String getVersionId()
168   {
169     return versionId;
170   }
171 
172   /**
173    * {@inheritDoc}
174    *
175    * @see de.smartics.maven.issues.ArtifactVersionRange#getVersionSpecification()
176    */
177   public String getVersionSpecification()
178   {
179     return versionSpecification;
180   }
181 
182   /**
183    * Returns the start index to use to construct the field properties of the
184    * query to Bugzilla.
185    *
186    * @return the start index to use to construct the field properties of the
187    *         query to Bugzilla.
188    */
189   public int getFieldStartIndex()
190   {
191     return fieldStartIndex;
192   }
193 
194   // --- business -------------------------------------------------------------
195 
196   /**
197    * {@inheritDoc}
198    *
199    * @see de.smartics.maven.issues.ArtifactVersionRange#containsVersion(org.apache.maven.artifact.versioning.ArtifactVersion)
200    */
201   public boolean containsVersion(final ArtifactVersion version)
202     throws NullPointerException
203   {
204     return range.containsVersion(version);
205   }
206 
207   /**
208    * {@inheritDoc}
209    *
210    * @see de.smartics.maven.issues.ArtifactVersionRange#appendToUrl(java.lang.StringBuilder)
211    */
212   @SuppressWarnings("unchecked")
213   public StringBuilder appendToUrl(final StringBuilder buffer)
214   {
215     final List<Restriction> restrictions =
216         (List<Restriction>) range.getRestrictions();
217     int index = fieldStartIndex;
218     for (Restriction restriction : restrictions)
219     {
220       appendAmpersand(buffer);
221       final ArtifactVersion lowerBound = restriction.getLowerBound();
222       final ArtifactVersion upperBound = restriction.getUpperBound();
223       if (ObjectUtils.equals(lowerBound, upperBound))
224       {
225         buffer.append(versionId).append('=').append(lowerBound.toString());
226       }
227       else
228       {
229         final int oldIndex = index;
230         index = appendLowerBound(buffer, index, restriction);
231         if (lowerBound != null && upperBound != null)
232         {
233           appendAmpersand(buffer);
234         }
235         index =
236             appendUpperBound(buffer, index, index == oldIndex ? 0 : 1,
237                 restriction);
238       }
239     }
240     return buffer;
241   }
242 
243   /**
244    * Appends the ampersand if necessary.
245    *
246    * @param buffer the buffer to append to.
247    */
248   private void appendAmpersand(final StringBuilder buffer)
249   {
250     final int lastIndex = buffer.length() - 1;
251     if (lastIndex >= 0)
252     {
253       final char lastChar = buffer.charAt(lastIndex);
254       if (lastChar != '?' && lastChar != '&')
255       {
256         buffer.append('&');
257       }
258     }
259   }
260 
261   // --- object basics --------------------------------------------------------
262 
263   /**
264    * Appends the query parts for the lower bound.
265    *
266    * @param buffer the buffer to append to.
267    * @param index the index to use to create query <code>field</code>
268    *          parameters.
269    * @param restriction the restriction to read the lower bound information
270    *          from.
271    * @return the new index to continue.
272    */
273   private int appendLowerBound(final StringBuilder buffer, final int index,
274       final Restriction restriction)
275   {
276     final ArtifactVersion lowerBound = restriction.getLowerBound();
277     if (lowerBound != null)
278     {
279       final String version = lowerBound.toString();
280       buffer.append(FIELD_PARAM_NAME).append(index).append("-0-0=")
281           .append(versionId).append('&').append(TYPE_PARAM_NAME).append(index)
282           .append("-0-0=greaterthan&").append(VALUE_PARAM_NAME).append(index)
283           .append("-0-0=").append(version);
284       if (restriction.isLowerBoundInclusive())
285       {
286         buffer.append('&').append(FIELD_PARAM_NAME).append(index)
287             .append("-0-1=").append(versionId).append('&')
288             .append(TYPE_PARAM_NAME).append(index).append("-0-1=equals&")
289             .append(VALUE_PARAM_NAME).append(index).append("-0-1=")
290             .append(version);
291       }
292       return index + 1;
293     }
294     return index;
295   }
296 
297   /**
298    * Appends the query parts for the upper bound.
299    *
300    * @param buffer the buffer to append to.
301    * @param index the index to use to create query <code>field</code> (first
302    *          dimension) parameters.
303    * @param index2 the index to use to create query <code>field</code> (second
304    *          dimension) parameters.
305    * @param restriction the restriction to read the upper bound information
306    *          from.
307    * @return the new index to continue.
308    */
309   private int appendUpperBound(final StringBuilder buffer, final int index,
310       final int index2, final Restriction restriction)
311   {
312     final ArtifactVersion upperBound = restriction.getUpperBound();
313     if (upperBound != null)
314     {
315       final String version = upperBound.toString();
316       buffer.append(FIELD_PARAM_NAME).append(index).append('-').append(index2)
317           .append("-0=").append(versionId).append('&').append(TYPE_PARAM_NAME)
318           .append(index).append('-').append(index2).append("-0=lessthan&")
319           .append(VALUE_PARAM_NAME).append(index).append('-').append(index2)
320           .append("-0=").append(version);
321       if (restriction.isUpperBoundInclusive())
322       {
323         buffer.append('&').append(FIELD_PARAM_NAME).append(index).append('-')
324             .append(index2).append("-1=").append(versionId).append('&')
325             .append(TYPE_PARAM_NAME).append(index).append('-').append(index2)
326             .append("-1=equals&").append(VALUE_PARAM_NAME).append(index)
327             .append('-').append(index2).append("-1=").append(version);
328       }
329       return index + 1;
330     }
331     return index;
332   }
333 
334   /**
335    * Reads the object from the given stream.
336    *
337    * @param in the stream to read from.
338    * @throws IOException on read problems.
339    * @throws ClassNotFoundException if a class cannot be found.
340    */
341   private void readObject(final ObjectInputStream in) throws IOException,
342     ClassNotFoundException
343   {
344     in.defaultReadObject();
345 
346     try
347     {
348       this.range = VersionRange.createFromVersionSpec(versionSpecification);
349     }
350     catch (final InvalidVersionSpecificationException e)
351     {
352       final IOException ex = new IOException("Invalid version specification.");
353       ex.initCause(e);
354       throw ex; // NOPMD: We set the cause in the line above.
355     }
356   }
357 
358   /**
359    * {@inheritDoc}
360    *
361    * @see de.smartics.maven.issues.ArtifactVersionRange#toString()
362    */
363   @Override
364   public String toString()
365   {
366     return versionSpecification;
367   }
368 }