PageRenderTime 53ms CodeModel.GetById 20ms RepoModel.GetById 0ms app.codeStats 1ms

/projects/geotools-9.2/modules/library/referencing/src/main/java/org/geotools/referencing/wkt/Element.java

https://gitlab.com/essere.lab.public/qualitas.class-corpus
Java | 578 lines | 282 code | 35 blank | 261 comment | 60 complexity | e821858b81f3f90abcfe4e013743d2f1 MD5 | raw file
  1. /*
  2. * GeoTools - The Open Source Java GIS Toolkit
  3. * http://geotools.org
  4. *
  5. * (C) 2002-2008, Open Source Geospatial Foundation (OSGeo)
  6. *
  7. * This library is free software; you can redistribute it and/or
  8. * modify it under the terms of the GNU Lesser General Public
  9. * License as published by the Free Software Foundation;
  10. * version 2.1 of the License.
  11. *
  12. * This library is distributed in the hope that it will be useful,
  13. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  14. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  15. * Lesser General Public License for more details.
  16. */
  17. package org.geotools.referencing.wkt;
  18. import java.io.PrintWriter;
  19. import java.text.ParseException;
  20. import java.text.ParsePosition;
  21. import java.util.Iterator;
  22. import java.util.LinkedList;
  23. import java.util.List;
  24. import org.geotools.util.Utilities;
  25. import org.geotools.resources.XArray;
  26. import org.geotools.resources.i18n.Errors;
  27. import org.geotools.resources.i18n.ErrorKeys;
  28. import org.geotools.util.logging.LoggedFormat;
  29. /**
  30. * An element in a <cite>Well Know Text</cite> (WKT). A {@code Element} is
  31. * made of {@link String}, {@link Number} and other {@link Element}. For example:
  32. *
  33. * <blockquote><pre>
  34. * PRIMEM["Greenwich", 0.0, AUTHORITY["some authority", "Greenwich"]]
  35. * </pre></blockquote>
  36. *
  37. * Each {@code Element} object can contains an arbitrary amount of other elements.
  38. * The result is a tree, which can be printed with {@link #print}.
  39. * Elements can be pull in a <cite>first in, first out</cite> order.
  40. *
  41. * @since 2.0
  42. *
  43. *
  44. * @source $URL$
  45. * @version $Id$
  46. * @author Remi Eve
  47. * @author Martin Desruisseaux (IRD)
  48. */
  49. public final class Element {
  50. /**
  51. * The position where this element starts in the string to be parsed.
  52. */
  53. private final int offset;
  54. /**
  55. * Keyword of this entity. For example: "PRIMEM".
  56. */
  57. public final String keyword;
  58. /**
  59. * An ordered list of {@link String}s, {@link Number}s and other {@link Element}s.
  60. * May be {@code null} if the keyword was not followed by a pair of brackets
  61. * (e.g. "NORTH").
  62. */
  63. private final List<Object> list;
  64. /**
  65. * Constructs a root element.
  66. *
  67. * @param singleton The only children for this root.
  68. */
  69. Element(final Element singleton) {
  70. offset = 0;
  71. keyword = null;
  72. list = new LinkedList<Object>();
  73. list.add(singleton);
  74. }
  75. /**
  76. * Constructs a new {@code Element}.
  77. *
  78. * @param text The text to parse.
  79. * @param position In input, the position where to start parsing from.
  80. * In output, the first character after the separator.
  81. */
  82. Element(final AbstractParser parser, final String text, final ParsePosition position)
  83. throws ParseException
  84. {
  85. /*
  86. * Find the first keyword in the specified string. If a keyword is found, then
  87. * the position is set to the index of the first character after the keyword.
  88. */
  89. int lower = position.getIndex();
  90. final int length = text.length();
  91. while (lower<length && Character.isWhitespace(text.charAt(lower))) {
  92. lower++;
  93. }
  94. offset = lower;
  95. int upper = lower;
  96. while (upper<length && Character.isUnicodeIdentifierPart(text.charAt(upper))) {
  97. upper++;
  98. }
  99. if (upper <= lower) {
  100. position.setErrorIndex(lower);
  101. throw unparsableString(text, position);
  102. }
  103. keyword = text.substring(lower, upper).toUpperCase(parser.symbols.locale);
  104. position.setIndex(upper);
  105. /*
  106. * Parse the opening bracket. According CTS's specification, two characters
  107. * are acceptable: '[' and '('. At the end of this method, we will require
  108. * the matching closing bracket. For example if the opening bracket was '[',
  109. * then we will require that the closing bracket is ']' and not ')'.
  110. */
  111. int bracketIndex = -1;
  112. do {
  113. if (++bracketIndex >= parser.symbols.openingBrackets.length) {
  114. list = null;
  115. return;
  116. }
  117. }
  118. while (!parseOptionalSeparator(text, position, parser.symbols.openingBrackets[bracketIndex]));
  119. list = new LinkedList<Object>();
  120. /*
  121. * Parse all elements inside the bracket. Elements are parsed sequentially
  122. * and their type are selected according their first character:
  123. *
  124. * - If the first character is a quote, then the element is parsed as a String.
  125. * - Otherwise, if the first character is a unicode identifier start, then the
  126. * element is parsed as a chidren Element.
  127. * - Otherwise, the element is parsed as a number.
  128. */
  129. do {
  130. if (position.getIndex() >= length) {
  131. throw missingCharacter(parser.symbols.close, length);
  132. }
  133. //
  134. // Try to parse the next element as a quoted string. We will take
  135. // it as a string if the first non-blank character is a quote.
  136. //
  137. if (parseOptionalSeparator(text, position, parser.symbols.quote)) {
  138. lower = position.getIndex();
  139. upper = text.indexOf(parser.symbols.quote, lower);
  140. if (upper < lower) {
  141. position.setErrorIndex(++lower);
  142. throw missingCharacter(parser.symbols.quote, lower);
  143. }
  144. list.add(text.substring(lower, upper).trim());
  145. position.setIndex(upper + 1);
  146. continue;
  147. }
  148. //
  149. // Try to parse the next element as a number. We will take it as a number if
  150. // the first non-blank character is not the begining of an unicode identifier.
  151. //
  152. lower = position.getIndex();
  153. if (!Character.isUnicodeIdentifierStart(text.charAt(lower))) {
  154. final Number number = parser.parseNumber(text, position);
  155. if (number == null) {
  156. // Do not update the error index; it is already updated by NumberFormat.
  157. throw unparsableString(text, position);
  158. }
  159. list.add(number);
  160. continue;
  161. }
  162. // Otherwise, add the element as a child element.
  163. list.add(new Element(parser, text, position));
  164. } while (parseOptionalSeparator(text, position, parser.symbols.separator));
  165. parseSeparator(text, position, parser.symbols.closingBrackets[bracketIndex]);
  166. }
  167. /**
  168. * Returns {@code true} if the next non-whitespace character is the specified separator.
  169. * Search is performed in string {@code text} from position {@code position}. If the
  170. * separator is found, then the position is set to the first character after the separator.
  171. * Otherwise, the position is set on the first non-blank character.
  172. *
  173. * @param text The text to parse.
  174. * @param position In input, the position where to start parsing from.
  175. * In output, the first character after the separator.
  176. * @param separator The character to search.
  177. * @return {@code true} if the next non-whitespace character is the separator,
  178. * or {@code false} otherwise.
  179. */
  180. private static boolean parseOptionalSeparator(final String text,
  181. final ParsePosition position,
  182. final char separator)
  183. {
  184. final int length = text.length();
  185. int index = position.getIndex();
  186. while (index < length) {
  187. final char c = text.charAt(index);
  188. if (Character.isWhitespace(c)) {
  189. index++;
  190. continue;
  191. }
  192. if (c == separator) {
  193. position.setIndex(++index);
  194. return true;
  195. }
  196. break;
  197. }
  198. position.setIndex(index); // MANDATORY for correct working of the constructor.
  199. return false;
  200. }
  201. /**
  202. * Moves to the next non-whitespace character and checks if this character is the
  203. * specified separator. If the separator is found, it is skipped. Otherwise, this
  204. * method thrown a {@link ParseException}.
  205. *
  206. * @param text The text to parse.
  207. * @param position In input, the position where to start parsing from.
  208. * In output, the first character after the separator.
  209. * @param separator The character to search.
  210. * @throws ParseException if the separator was not found.
  211. */
  212. private void parseSeparator(final String text,
  213. final ParsePosition position,
  214. final char separator)
  215. throws ParseException
  216. {
  217. if (!parseOptionalSeparator(text, position, separator)) {
  218. position.setErrorIndex(position.getIndex());
  219. throw unparsableString(text, position);
  220. }
  221. }
  222. //////////////////////////////////////////////////////////////////////////////////////
  223. //////// ////////
  224. //////// Construction of a ParseException when a string can't be parsed ////////
  225. //////// ////////
  226. //////////////////////////////////////////////////////////////////////////////////////
  227. /**
  228. * Returns a {@link ParseException} with the specified cause. A localized string
  229. * <code>"Error in <{@link #keyword}>"</code> will be prepend to the message.
  230. * The error index will be the starting index of this {@code Element}.
  231. *
  232. * @param cause The cause of the failure, or {@code null} if none.
  233. * @param message The message explaining the cause of the failure, or {@code null}
  234. * for reusing the same message than {@code cause}.
  235. * @return The exception to be thrown.
  236. */
  237. public ParseException parseFailed(final Exception cause, String message) {
  238. if (message == null) {
  239. message = cause.getLocalizedMessage();
  240. }
  241. ParseException exception = new ParseException(complete(message), offset);
  242. exception = trim("parseFailed", exception);
  243. exception.initCause(cause);
  244. return exception;
  245. }
  246. /**
  247. * Returns a {@link ParseException} with a "Unparsable string" message. The error message
  248. * is built from the specified string starting at the specified position. Properties
  249. * {@link ParsePosition#getIndex} and {@link ParsePosition#getErrorIndex} must be accurate
  250. * before this method is invoked.
  251. *
  252. * @param text The unparsable string.
  253. * @param position The position in the string.
  254. * @return An exception with a formatted error message.
  255. */
  256. private ParseException unparsableString(final String text, final ParsePosition position) {
  257. final int errorIndex = position.getErrorIndex();
  258. String message = LoggedFormat.formatUnparsable(text, position.getIndex(), errorIndex, null);
  259. message = complete(message);
  260. return trim("unparsableString", new ParseException(message, errorIndex));
  261. }
  262. /**
  263. * Returns an exception saying that a character is missing.
  264. *
  265. * @param c The missing character.
  266. * @param position The error position.
  267. */
  268. private ParseException missingCharacter(final char c, final int position) {
  269. return trim("missingCharacter", new ParseException(complete(
  270. Errors.format(ErrorKeys.MISSING_CHARACTER_$1, Character.valueOf(c))), position));
  271. }
  272. /**
  273. * Returns an exception saying that a parameter is missing.
  274. *
  275. * @param key The name of the missing parameter.
  276. */
  277. private ParseException missingParameter(final String key) {
  278. int error = offset;
  279. if (keyword != null) {
  280. error += keyword.length();
  281. }
  282. return trim("missingParameter", new ParseException(complete(
  283. Errors.format(ErrorKeys.MISSING_PARAMETER_$1, key)), error));
  284. }
  285. /**
  286. * Append a prefix "Error in <keyword>: " before the error message.
  287. *
  288. * @param message The message to complete.
  289. * @return The completed message.
  290. */
  291. private String complete(String message) {
  292. if (keyword != null) {
  293. message = Errors.format(ErrorKeys.IN_$1, keyword) + ' ' + message;
  294. }
  295. return message;
  296. }
  297. /**
  298. * Remove the exception factory method from the stack trace. The factory is
  299. * not the place where the failure occurs; the error occurs in the factory's
  300. * caller.
  301. *
  302. * @param factory The name of the factory method.
  303. * @param exception The exception to trim.
  304. * @return {@code exception} for convenience.
  305. */
  306. private static ParseException trim(final String factory, final ParseException exception) {
  307. StackTraceElement[] trace = exception.getStackTrace();
  308. if (trace!=null && trace.length!=0) {
  309. if (factory.equals(trace[0].getMethodName())) {
  310. trace = XArray.remove(trace, 0, 1);
  311. exception.setStackTrace(trace);
  312. }
  313. }
  314. return exception;
  315. }
  316. /**
  317. * Returns {@code true} if this element is the root element. For example in a WKT like
  318. * {@code "GEOGCS["name", DATUM["name, ...]]"}, this is true for {@code "GEOGCS"} and
  319. * false for all other elements inside, like {@code "DATUM"}.
  320. *
  321. * @return {@code true} if this element is the root element.
  322. *
  323. * @since 2.3
  324. */
  325. public boolean isRoot() {
  326. return this.offset == 0;
  327. }
  328. //////////////////////////////////////////////////////////////////////////////////////
  329. //////// ////////
  330. //////// Pull elements from the tree ////////
  331. //////// ////////
  332. //////////////////////////////////////////////////////////////////////////////////////
  333. /**
  334. * Removes the next {@link Number} from the list and returns it.
  335. *
  336. * @param key The parameter name. Used for formatting
  337. * an error message if no number are found.
  338. * @return The next {@link Number} on the list as a {@code double}.
  339. * @throws ParseException if no more number is available.
  340. */
  341. public double pullDouble(final String key) throws ParseException {
  342. final Iterator iterator = list.iterator();
  343. while (iterator.hasNext()) {
  344. final Object object = iterator.next();
  345. if (object instanceof Number) {
  346. iterator.remove();
  347. return ((Number)object).doubleValue();
  348. }
  349. }
  350. throw missingParameter(key);
  351. }
  352. /**
  353. * Removes the next {@link Number} from the list and returns it
  354. * as an integer.
  355. *
  356. * @param key The parameter name. Used for formatting
  357. * an error message if no number are found.
  358. * @return The next {@link Number} on the list as an {@code int}.
  359. * @throws ParseException if no more number is available, or the number
  360. * is not an integer.
  361. */
  362. public int pullInteger(final String key) throws ParseException {
  363. final Iterator iterator = list.iterator();
  364. while (iterator.hasNext()) {
  365. final Object object = iterator.next();
  366. if (object instanceof Number) {
  367. iterator.remove();
  368. final Number number = (Number) object;
  369. if (number instanceof Float || number instanceof Double) {
  370. throw new ParseException(complete(Errors.format(
  371. ErrorKeys.ILLEGAL_ARGUMENT_$2, key, number)), offset);
  372. }
  373. return number.intValue();
  374. }
  375. }
  376. throw missingParameter(key);
  377. }
  378. /**
  379. * Removes the next {@link String} from the list and returns it.
  380. *
  381. * @param key The parameter name. Used for formatting
  382. * an error message if no number are found.
  383. * @return The next {@link String} on the list.
  384. * @throws ParseException if no more string is available.
  385. */
  386. public String pullString(final String key) throws ParseException {
  387. String optionalString = pullOptionalString(key);
  388. if (optionalString != null) {
  389. return optionalString;
  390. }
  391. throw missingParameter(key);
  392. }
  393. /**
  394. * Removes the next {@link String} from the list and returns it.
  395. * @param key The parameter name. Used for formatting
  396. * an error message if no number are found.
  397. * @return The next {@link String} on the list
  398. * or {@code null} if no more element is available.
  399. */
  400. public String pullOptionalString(final String key) {
  401. final Iterator iterator = list.iterator();
  402. while (iterator.hasNext()) {
  403. final Object object = iterator.next();
  404. if (object instanceof String) {
  405. iterator.remove();
  406. return (String)object;
  407. }
  408. }
  409. return null;
  410. }
  411. /**
  412. * Removes the next {@link Element} from the list and returns it.
  413. *
  414. * @param key The element name (e.g. <code>"PRIMEM"</code>).
  415. * @return The next {@link Element} on the list.
  416. * @throws ParseException if no more element is available.
  417. */
  418. public Element pullElement(final String key) throws ParseException {
  419. final Element element = pullOptionalElement(key);
  420. if (element != null) {
  421. return element;
  422. }
  423. throw missingParameter(key);
  424. }
  425. /**
  426. * Removes the next {@link Element} from the list and returns it.
  427. *
  428. * @param key The element name (e.g. <code>"PRIMEM"</code>).
  429. * @return The next {@link Element} on the list,
  430. * or {@code null} if no more element is available.
  431. */
  432. public Element pullOptionalElement(String key) {
  433. key = key.toUpperCase();
  434. final Iterator iterator = list.iterator();
  435. while (iterator.hasNext()) {
  436. final Object object = iterator.next();
  437. if (object instanceof Element) {
  438. final Element element = (Element) object;
  439. if (element.list!=null && element.keyword.equals(key)) {
  440. iterator.remove();
  441. return element;
  442. }
  443. }
  444. }
  445. return null;
  446. }
  447. /**
  448. * Removes and returns the next {@link Element} with no bracket.
  449. * The key is used only for only for formatting an error message.
  450. *
  451. * @param key The parameter name. Used only for formatting an error message.
  452. * @return The next {@link Element} in the list, with no bracket.
  453. * @throws ParseException if no more void element is available.
  454. */
  455. public Element pullVoidElement(final String key) throws ParseException {
  456. final Iterator iterator = list.iterator();
  457. while (iterator.hasNext()) {
  458. final Object object = iterator.next();
  459. if (object instanceof Element) {
  460. final Element element = (Element) object;
  461. if (element.list == null) {
  462. iterator.remove();
  463. return element;
  464. }
  465. }
  466. }
  467. throw missingParameter(key);
  468. }
  469. /**
  470. * Removes and returns the next {@link Element} with no bracket, if available, or
  471. * null otherwise.
  472. * @return The next {@link Element} in the list, with no bracket, or null if none was found
  473. * @throws ParseException if no more void element is available.
  474. */
  475. public Element pullOptionalVoidElement() throws ParseException {
  476. final Iterator iterator = list.iterator();
  477. while (iterator.hasNext()) {
  478. final Object object = iterator.next();
  479. if (object instanceof Element) {
  480. final Element element = (Element) object;
  481. if (element.list == null) {
  482. iterator.remove();
  483. return element;
  484. }
  485. }
  486. }
  487. return null;
  488. }
  489. /**
  490. * Returns the next element, or {@code null} if there is no more
  491. * element. The element is <strong>not</strong> removed from the list.
  492. *
  493. * @return The next element, or {@code null} if there is no more elements.
  494. */
  495. public Object peek() {
  496. return list.isEmpty() ? null : list.get(0);
  497. }
  498. /**
  499. * Close this element.
  500. *
  501. * @throws ParseException If the list still contains some unprocessed elements.
  502. */
  503. public void close() throws ParseException {
  504. if (list!=null && !list.isEmpty()) {
  505. throw new ParseException(complete(Errors.format(ErrorKeys.UNEXPECTED_PARAMETER_$1,
  506. list.get(0))), offset+keyword.length());
  507. }
  508. }
  509. /**
  510. * Returns the keyword. This overriding is needed for correct
  511. * formatting of the error message in {@link #close}.
  512. */
  513. @Override
  514. public String toString() {
  515. return keyword;
  516. }
  517. /**
  518. * Print this {@code Element} as a tree.
  519. * This method is used for debugging purpose only.
  520. *
  521. * @param out The output stream.
  522. * @param level The indentation level (usually 0).
  523. */
  524. public void print(final PrintWriter out, final int level) {
  525. final int tabWidth = 4;
  526. out.print(Utilities.spaces(tabWidth * level));
  527. out.println(keyword);
  528. if (list == null) {
  529. return;
  530. }
  531. final int size = list.size();
  532. for (int j=0; j<size; j++) {
  533. final Object object = list.get(j);
  534. if (object instanceof Element) {
  535. ((Element)object).print(out, level+1);
  536. } else {
  537. out.print(Utilities.spaces(tabWidth * (level+1)));
  538. out.println(object);
  539. }
  540. }
  541. }
  542. }