View Javadoc

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