View Javadoc

1   /*
2    * Copyright 2011-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.util.source;
17  
18  import java.io.Serializable;
19  
20  /**
21   * Provides information about a source code location.
22   *
23   * @todo Serial: check validity of lines and columns when read from stream.
24   */
25  public class SourceCodeLocation implements Serializable
26  {
27    // ********************************* Fields *********************************
28  
29    // --- constants ------------------------------------------------------------
30  
31    /**
32     * The class version identifier.
33     * <p>
34     * The value of this constant is {@value}.
35     * </p>
36     */
37    private static final long serialVersionUID = 1L;
38  
39    /**
40     * Defines an unknown location. The fields must not be accessed.
41     */
42    public static final SourceCodeLocation UNKNOWN_LOCATION =
43        new SourceCodeLocation(Position.INVALID_POSITION,
44            Position.INVALID_POSITION)
45        {
46          private static final long serialVersionUID = 1L;
47  
48          @Override
49          public String toString()
50          {
51            return "INVALID";
52          }
53        };
54  
55    // --- members --------------------------------------------------------------
56  
57    /**
58     * The start position of the source code location.
59     *
60     * @serial
61     */
62    private final Position start;
63  
64    /**
65     * The end position of the source code location.
66     *
67     * @serial
68     */
69    private final Position end;
70  
71    // ****************************** Initializer *******************************
72  
73    // ****************************** Constructors ******************************
74  
75    /**
76     * Default constructor.
77     *
78     * @param start the start position of the source code location.
79     * @param end the end position of the source code location.
80     * @throws IllegalArgumentException if <code>start</code> or <code>end</code>
81     *           are <code>null</code> or <code>start</code> position is greater
82     *           than the <code>end</code> position.
83     */
84    public SourceCodeLocation(final Position start, final Position end)
85      throws IllegalArgumentException
86    {
87      checkArguments(start, end);
88      this.start = start;
89      this.end = end;
90    }
91  
92    // ****************************** Inner Classes *****************************
93  
94    /**
95     * Defines a position in the source code.
96     *
97     * @todo Serial: check validity of lines and columns when read from stream.
98     */
99    public static class Position implements Serializable, Comparable<Position>
100   {
101     // ******************************** Fields ********************************
102 
103     // --- constants ----------------------------------------------------------
104 
105     /**
106      * The class version identifier.
107      * <p>
108      * The value of this constant is {@value}.
109      * </p>
110      */
111     private static final long serialVersionUID = 1L;
112 
113     /**
114      * An invalid position.
115      */
116     private static final Position INVALID_POSITION = new Position() // NOPMD
117         {
118           private static final long serialVersionUID = 1L;
119 
120           @Override
121           public String toString()
122           {
123             return "INVALID";
124           }
125         };
126 
127     // --- members ------------------------------------------------------------
128     /**
129      * The line number of the position. The lines start by <code>1</code>.
130      *
131      * @serial
132      */
133     private final long line;
134 
135     /**
136      * The column number of the position. The columns start by <code>1</code>.
137      *
138      * @serial
139      */
140     private final long column;
141 
142     // ***************************** Initializer ******************************
143 
144     // ***************************** Constructors *****************************
145 
146     /**
147      * Invalid instance constructor.
148      */
149     private Position()
150     {
151       this.line = 0;
152       this.column = 0;
153     }
154 
155     /**
156      * Default constructor.
157      *
158      * @param line the line number of the position.
159      * @param column the column number of the position.
160      * @throws IllegalArgumentException if <code>line</code> or
161      *           <code>column</code> is smaller than <code>1</code>.
162      */
163     public Position(final long line, final long column)
164       throws IllegalArgumentException
165     {
166       checkArguments(line, column);
167       this.line = line;
168       this.column = column;
169     }
170 
171     // ***************************** Inner Classes ****************************
172 
173     // ******************************** Methods *******************************
174 
175     // --- init ---------------------------------------------------------------
176 
177     private static void checkArguments(final long line, final long column)
178       throws IllegalArgumentException
179     {
180       if (line < 1L)
181       {
182         throw new IllegalArgumentException(
183             "The line number is required to be within [1, MAX_LONG], but is: "
184                 + line);
185       }
186       if (column < 1L)
187       {
188         throw new IllegalArgumentException(
189             "The column number is required to be within [1, MAX_LONG], but is: "
190                 + column);
191       }
192     }
193 
194     // --- get&set ------------------------------------------------------------
195 
196     /**
197      * Returns the line number of the position. The lines start by
198      * <code>1</code>.
199      *
200      * @return the line number of the position.
201      */
202     public long getLine()
203     {
204       return line;
205     }
206 
207     /**
208      * Returns the column number of the position. The columns start by
209      * <code>1</code>.
210      *
211      * @return the column number of the position.
212      */
213     public long getColumn()
214     {
215       return column;
216     }
217 
218     // --- business -----------------------------------------------------------
219 
220     // --- object basics ------------------------------------------------------
221 
222     /**
223      * {@inheritDoc}
224      */
225     @Override
226     public int compareTo(final Position other)
227     {
228       if (line == other.line)
229       {
230         final long columnCompare = column - other.column;
231         if (columnCompare < 0)
232         {
233           return -1;
234         }
235         else if (columnCompare > 0)
236         {
237           return 1;
238         }
239         else
240         {
241           return 0;
242         }
243       }
244       else
245       {
246         final long lineCompare = line - other.line;
247         if (lineCompare < 0)
248         {
249           return -1;
250         }
251         else
252         {
253           return 1;
254         }
255       }
256     }
257 
258     /**
259      * Returns the hash code of the object.
260      *
261      * @return the hash code.
262      */
263     @Override
264     public int hashCode()
265     {
266       int result = 17;
267       result = 37 * result + (int) (line ^ (line >>> 32));
268       result = 37 * result + (int) (column ^ (column >>> 32));
269       return result;
270     }
271 
272     /**
273      * Returns <code>true</code> if the given object is semantically equal to
274      * the given object, <code>false</code> otherwise.
275      *
276      * @param object the instance to compare to.
277      * @return <code>true</code> if the given object is semantically equal to
278      *         the given object, <code>false</code> otherwise.
279      */
280     @Override
281     public boolean equals(final Object object)
282     {
283       if (this == object)
284       {
285         return true;
286       }
287       else if (object == null || getClass() != object.getClass())
288       {
289         return false;
290       }
291 
292       final SourceCodeLocation.Position other =
293           (SourceCodeLocation.Position) object;
294 
295       return (line == other.line && column == other.column);
296     }
297 
298     /**
299      * Delegates call to {@link java.lang.StringBuilder#toString()}.
300      *
301      * @return the result of the call to
302      *         {@link java.lang.StringBuilder#toString()}.
303      * @see java.lang.StringBuilder#toString()
304      */
305     @Override
306     public String toString()
307     {
308       final StringBuilder buffer = new StringBuilder();
309       buffer.append(line).append(':').append(column);
310       return buffer.toString();
311     }
312   }
313 
314   // ********************************* Methods ********************************
315 
316   // --- init -----------------------------------------------------------------
317 
318   private static void checkArguments(final Position start, final Position end)
319     throws IllegalArgumentException
320   {
321     if (start == null)
322     {
323       throw new IllegalArgumentException(
324           "The start position of the location is required.");
325     }
326     if (end == null)
327     {
328       throw new IllegalArgumentException(
329           "The end position of the location is required.");
330     }
331     if (start.compareTo(end) > 0)
332     {
333       throw new IllegalArgumentException(
334           "The start position '" + start
335               + " must not be after the end position " + end + "'.");
336     }
337   }
338 
339   // --- get&set --------------------------------------------------------------
340 
341   /**
342    * Returns the start position of the source code location.
343    *
344    * @return the start position of the source code location.
345    */
346   public Position getStart()
347   {
348     return start;
349   }
350 
351   /**
352    * Returns the end position of the source code location.
353    *
354    * @return the end position of the source code location.
355    */
356   public Position getEnd()
357   {
358     return end;
359   }
360 
361   // --- business -------------------------------------------------------------
362 
363   /**
364    * Checks if the location is known.
365    *
366    * @return <code>true</code> if it is a known location with valid content,
367    *         <code>false</code> if it is an unknown location identified by
368    *         {@link #UNKNOWN_LOCATION}.
369    */
370   public boolean isKnown()
371   {
372     return this != UNKNOWN_LOCATION;
373   }
374 
375   // --- object basics --------------------------------------------------------
376 
377   /**
378    * Returns the string representation of the object.
379    *
380    * @return the string representation of the object.
381    */
382   @Override
383   public String toString()
384   {
385     final StringBuilder buffer = new StringBuilder();
386 
387     buffer.append(start).append('-').append(end);
388 
389     return buffer.toString();
390   }
391 }