View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.maven.api.model;
20  
21  import java.io.Serializable;
22  import java.util.Collection;
23  import java.util.Collections;
24  import java.util.LinkedHashMap;
25  import java.util.Map;
26  import java.util.Objects;
27  
28  /**
29   * Represents the location of an element within a model source file.
30   * <p>
31   * This class tracks the line and column numbers of elements in source files like POM files.
32   * It's used for error reporting and debugging to help identify where specific model elements
33   * are defined in the source files.
34   *
35   * @since 4.0.0
36   */
37  public class InputLocation implements Serializable, InputLocationTracker {
38      private final int lineNumber;
39      private final int columnNumber;
40      private final InputSource source;
41      private final Map<Object, InputLocation> locations;
42      private final InputLocation importedFrom;
43  
44      public InputLocation(InputSource source) {
45          this.lineNumber = -1;
46          this.columnNumber = -1;
47          this.source = source;
48          this.locations = Collections.singletonMap(0, this);
49          this.importedFrom = null;
50      }
51  
52      public InputLocation(int lineNumber, int columnNumber) {
53          this(lineNumber, columnNumber, null, null);
54      }
55  
56      public InputLocation(int lineNumber, int columnNumber, InputSource source) {
57          this(lineNumber, columnNumber, source, null);
58      }
59  
60      public InputLocation(int lineNumber, int columnNumber, InputSource source, Object selfLocationKey) {
61          this.lineNumber = lineNumber;
62          this.columnNumber = columnNumber;
63          this.source = source;
64          this.locations =
65                  selfLocationKey != null ? Collections.singletonMap(selfLocationKey, this) : Collections.emptyMap();
66          this.importedFrom = null;
67      }
68  
69      public InputLocation(int lineNumber, int columnNumber, InputSource source, Map<Object, InputLocation> locations) {
70          this.lineNumber = lineNumber;
71          this.columnNumber = columnNumber;
72          this.source = source;
73          this.locations = ImmutableCollections.copy(locations);
74          this.importedFrom = null;
75      }
76  
77      public InputLocation(InputLocation original) {
78          this.lineNumber = original.lineNumber;
79          this.columnNumber = original.columnNumber;
80          this.source = original.source;
81          this.locations = original.locations;
82          this.importedFrom = original.importedFrom;
83      }
84  
85      public int getLineNumber() {
86          return lineNumber;
87      }
88  
89      public int getColumnNumber() {
90          return columnNumber;
91      }
92  
93      public InputSource getSource() {
94          return source;
95      }
96  
97      @Override
98      public InputLocation getLocation(Object key) {
99          Objects.requireNonNull(key, "key");
100         return locations != null ? locations.get(key) : null;
101     }
102 
103     public Map<Object, InputLocation> getLocations() {
104         return locations;
105     }
106 
107     /**
108      * Gets the parent InputLocation where this InputLocation may have been imported from.
109      * Can return {@code null}.
110      *
111      * @return InputLocation
112      * @since 4.0.0
113      */
114     @Override
115     public InputLocation getImportedFrom() {
116         return importedFrom;
117     }
118 
119     /**
120      * Merges the {@code source} location into the {@code target} location.
121      *
122      * @param target the target location
123      * @param source the source location
124      * @param sourceDominant the boolean indicating of {@code source} is dominant compared to {@code target}
125      * @return the merged location
126      */
127     public static InputLocation merge(InputLocation target, InputLocation source, boolean sourceDominant) {
128         if (source == null) {
129             return target;
130         } else if (target == null) {
131             return source;
132         }
133 
134         Map<Object, InputLocation> locations;
135         Map<Object, InputLocation> sourceLocations = source.locations;
136         Map<Object, InputLocation> targetLocations = target.locations;
137         if (sourceLocations == null) {
138             locations = targetLocations;
139         } else if (targetLocations == null) {
140             locations = sourceLocations;
141         } else {
142             locations = new LinkedHashMap<>();
143             locations.putAll(sourceDominant ? targetLocations : sourceLocations);
144             locations.putAll(sourceDominant ? sourceLocations : targetLocations);
145         }
146 
147         return new InputLocation(-1, -1, InputSource.merge(source.getSource(), target.getSource()), locations);
148     } // -- InputLocation merge( InputLocation, InputLocation, boolean )
149 
150     /**
151      * Merges the {@code source} location into the {@code target} location.
152      * This method is used when the locations refer to lists and also merges the indices.
153      *
154      * @param target the target location
155      * @param source the source location
156      * @param indices the list of integers for the indices
157      * @return the merged location
158      */
159     public static InputLocation merge(InputLocation target, InputLocation source, Collection<Integer> indices) {
160         if (source == null) {
161             return target;
162         } else if (target == null) {
163             return source;
164         }
165 
166         Map<Object, InputLocation> locations;
167         Map<Object, InputLocation> sourceLocations = source.locations;
168         Map<Object, InputLocation> targetLocations = target.locations;
169         if (sourceLocations == null) {
170             locations = targetLocations;
171         } else if (targetLocations == null) {
172             locations = sourceLocations;
173         } else {
174             locations = new LinkedHashMap<>();
175             for (int index : indices) {
176                 InputLocation location;
177                 if (index < 0) {
178                     location = sourceLocations.get(~index);
179                 } else {
180                     location = targetLocations.get(index);
181                 }
182                 locations.put(locations.size(), location);
183             }
184         }
185 
186         return new InputLocation(-1, -1, InputSource.merge(source.getSource(), target.getSource()), locations);
187     } // -- InputLocation merge( InputLocation, InputLocation, java.util.Collection )
188 
189     /**
190      * Class StringFormatter.
191      *
192      * @version $Revision$ $Date$
193      */
194     public interface StringFormatter {
195 
196         // -----------/
197         // - Methods -/
198         // -----------/
199 
200         /**
201          * Method toString.
202          */
203         String toString(InputLocation location);
204     }
205 
206     @Override
207     public String toString() {
208         return String.format("%s @ %d:%d", source != null ? source.getLocation() : "n/a", lineNumber, columnNumber);
209     }
210 }