Du kan inte välja fler än 25 ämnen Ämnen måste starta med en bokstav eller siffra, kan innehålla bindestreck ('-') och vara max 35 tecken långa.

Module.java 25KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754
  1. /* *******************************************************************
  2. * Copyright (c) 1999-2001 Xerox Corporation,
  3. * 2002 Palo Alto Research Center, Incorporated (PARC).
  4. * All rights reserved.
  5. * This program and the accompanying materials are made available
  6. * under the terms of the Eclipse Public License v1.0
  7. * which accompanies this distribution and is available at
  8. * http://www.eclipse.org/legal/epl-v10.html
  9. *
  10. * Contributors:
  11. * Xerox/PARC initial implementation
  12. * ******************************************************************/
  13. package org.aspectj.internal.tools.build;
  14. import java.io.BufferedReader;
  15. import java.io.ByteArrayOutputStream;
  16. import java.io.File;
  17. import java.io.FileInputStream;
  18. import java.io.FileReader;
  19. import java.io.IOException;
  20. import java.io.InputStream;
  21. import java.io.PrintStream;
  22. import java.util.ArrayList;
  23. import java.util.Iterator;
  24. import java.util.List;
  25. import java.util.ListIterator;
  26. import java.util.Properties;
  27. import java.util.StringTokenizer;
  28. import org.aspectj.internal.tools.build.Result.Kind;
  29. import org.aspectj.internal.tools.build.Util.OSGIBundle;
  30. import org.aspectj.internal.tools.build.Util.OSGIBundle.RequiredBundle;
  31. /**
  32. * This represents an (eclipse) build module/unit used by a Builder to compile
  33. * classes and/or assemble zip file of classes, optionally with all antecedants.
  34. * This implementation infers attributes from two files in the module directory:
  35. * <ul>
  36. * <li>an Eclipse project <code>.classpath</code> file containing required
  37. * libraries and modules (collectively, "antecedants") </li>
  38. * <li>a file <code>{moduleName}.mf.txt</code> is taken as the manifest of
  39. * any .jar file produced, after filtering. </li>
  40. * </ul>
  41. *
  42. * @see Builder
  43. * @see Modules#getModule(String)
  44. */
  45. public class Module {
  46. private static final String[] ATTS = new String[] { "exported", "kind",
  47. "path", "sourcepath" };
  48. // private static final int getATTSIndex(String key) {
  49. // for (int i = 0; i < ATTS.length; i++) {
  50. // if (ATTS[i].equals(key))
  51. // return i;
  52. // }
  53. // return -1;
  54. // }
  55. /**
  56. * @return true if file is null or cannot be read or was last modified after
  57. * time
  58. */
  59. private static boolean outOfDate(long time, File file) {
  60. return ((null == file) || !file.canRead() || (file.lastModified() > time));
  61. }
  62. /** @return all source files under srcDir */
  63. private static Iterator<File> sourceFiles(File srcDir) {
  64. ArrayList<File> result = new ArrayList<File>();
  65. sourceFiles(srcDir, result);
  66. return result.iterator();
  67. }
  68. private static void sourceFiles(File srcDir, List<File> result) {
  69. if ((null == srcDir) || !srcDir.canRead() || !srcDir.isDirectory()) {
  70. return;
  71. }
  72. File[] files = srcDir.listFiles();
  73. for (int i = 0; i < files.length; i++) {
  74. if (files[i].isDirectory()) {
  75. sourceFiles(files[i], result);
  76. } else if (isSourceFile(files[i])) {
  77. result.add(files[i]);
  78. }
  79. }
  80. }
  81. private static void addIfNew(List<File> source, List<File> sink) {
  82. for (File item: source) {
  83. if (!sink.contains(item)) {
  84. sink.add(item);
  85. }
  86. }
  87. }
  88. /**
  89. * Recursively find antecedant jars.
  90. *
  91. * @see findKnownJarAntecedants()
  92. */
  93. static void doFindJarRequirements(Result result, List<File> known) {
  94. Util.iaxIfNull(result, "result");
  95. Util.iaxIfNull(known, "known");
  96. addIfNew(result.getLibJars(), known);
  97. addIfNew(result.getExportedLibJars(), known);
  98. Result[] reqs = result.getRequired();
  99. for (int i = 0; i < reqs.length; i++) {
  100. Result requiredResult = reqs[i];
  101. File requiredJar = requiredResult.getOutputFile();
  102. if (!known.contains(requiredJar)) {
  103. known.add(requiredJar);
  104. doFindJarRequirements(requiredResult, known);
  105. }
  106. }
  107. }
  108. /** @return true if this is a source file */
  109. private static boolean isSourceFile(File file) {
  110. String path = file.getPath();
  111. return (path.endsWith(".java") || path.endsWith(".aj")); // XXXFileLiteral
  112. }
  113. // /** @return List of File of any module or library jar ending with suffix */
  114. // private static ArrayList findJarsBySuffix(String suffix, Kind kind,
  115. // List libJars, List required) {
  116. // ArrayList result = new ArrayList();
  117. // if (null != suffix) {
  118. // // library jars
  119. // for (Iterator iter = libJars.iterator(); iter.hasNext();) {
  120. // File file = (File) iter.next();
  121. // if (file.getPath().endsWith(suffix)) {
  122. // result.add(file);
  123. // }
  124. // }
  125. // // module jars
  126. // for (Iterator iter = required.iterator(); iter.hasNext();) {
  127. // Module module = (Module) iter.next();
  128. // Result moduleResult = module.getResult(kind);
  129. // File file = moduleResult.getOutputFile();
  130. // if (file.getPath().endsWith(suffix)) {
  131. // result.add(file);
  132. // }
  133. // }
  134. // }
  135. // return result;
  136. // }
  137. public final boolean valid;
  138. public final File moduleDir;
  139. public final String name;
  140. /** reference back to collection for creating required modules */
  141. private final Modules modules;
  142. private final Result release;
  143. private final Result test;
  144. private final Result testAll;
  145. private final Result releaseAll;
  146. /** path to output jar - may not exist */
  147. private final File moduleJar;
  148. /** File list of library jars */
  149. private final List<File> libJars;
  150. /** List of classpath variables */
  151. private final List<String> classpathVariables;
  152. /**
  153. * List of library jars exported to clients (duplicates some libJars
  154. * entries)
  155. */
  156. private final List<File> exportedLibJars;
  157. /** File list of source directories */
  158. private final List<File> srcDirs;
  159. /** properties from the modules {name}.properties file */
  160. private final Properties properties;
  161. /** List of required modules */
  162. private final List<Module> requiredModules;
  163. /** logger */
  164. private final Messager messager;
  165. Module(File moduleDir, File jarDir, String name, Modules modules,
  166. Messager messager) {
  167. Util.iaxIfNotCanReadDir(moduleDir, "moduleDir");
  168. Util.iaxIfNotCanReadDir(jarDir, "jarDir");
  169. Util.iaxIfNull(name, "name");
  170. Util.iaxIfNull(modules, "modules");
  171. this.moduleDir = moduleDir;
  172. this.libJars = new ArrayList<File>();
  173. this.exportedLibJars = new ArrayList<File>();
  174. this.requiredModules = new ArrayList<Module>();
  175. this.srcDirs = new ArrayList<File>();
  176. this.classpathVariables = new ArrayList<String>();
  177. this.properties = new Properties();
  178. this.name = name;
  179. this.modules = modules;
  180. this.messager = messager;
  181. this.moduleJar = new File(jarDir, name + ".jar");
  182. this.release = new Result(Result.RELEASE, this, jarDir);
  183. this.releaseAll = new Result(Result.RELEASE_ALL, this, jarDir);
  184. this.test = new Result(Result.TEST, this, jarDir);
  185. this.testAll = new Result(Result.TEST_ALL, this, jarDir);
  186. valid = init();
  187. }
  188. /** @return Modules registry of known modules, including this one */
  189. public Modules getModules() {
  190. return modules;
  191. }
  192. /**
  193. * @param kind
  194. * the Kind of the result to recalculate
  195. * @param recalculate
  196. * if true, then force recalculation
  197. * @return true if the target jar for this module is older than any source
  198. * files in a source directory or any required modules or any
  199. * libraries or if any libraries or required modules are missing
  200. */
  201. public static boolean outOfDate(Result result) {
  202. File outputFile = result.getOutputFile();
  203. if (!(outputFile.exists() && outputFile.canRead())) {
  204. return true;
  205. }
  206. final long time = outputFile.lastModified();
  207. File file;
  208. for (Iterator<File> iter = result.getSrcDirs().iterator(); iter.hasNext();) {
  209. File srcDir = iter.next();
  210. for (Iterator<File> srcFiles = sourceFiles(srcDir); srcFiles.hasNext();) {
  211. file = srcFiles.next();
  212. if (outOfDate(time, file)) {
  213. return true;
  214. }
  215. }
  216. }
  217. // required modules
  218. Result[] reqs = result.getRequired();
  219. for (int i = 0; i < reqs.length; i++) {
  220. Result requiredResult = reqs[i];
  221. file = requiredResult.getOutputFile();
  222. if (outOfDate(time, file)) {
  223. return true;
  224. }
  225. }
  226. // libraries
  227. for (Iterator iter = result.getLibJars().iterator(); iter.hasNext();) {
  228. file = (File) iter.next();
  229. if (outOfDate(time, file)) {
  230. return true;
  231. }
  232. }
  233. return false;
  234. }
  235. public String toString() {
  236. return name;
  237. }
  238. public String toLongString() {
  239. return "Module [name=" + name + ", srcDirs=" + srcDirs + ", required="
  240. + requiredModules + ", moduleJar=" + moduleJar + ", libJars="
  241. + libJars + "]";
  242. }
  243. public Result getResult(Kind kind) {
  244. return kind.assemble ? (kind.normal ? releaseAll : testAll)
  245. : (kind.normal ? release : test);
  246. }
  247. List<File> srcDirs(Result result) {
  248. myResult(result);
  249. return srcDirs;
  250. }
  251. List<File> libJars(Result result) {
  252. myResult(result);
  253. return libJars;
  254. }
  255. List<String> classpathVariables(Result result) {
  256. myResult(result);
  257. return classpathVariables;
  258. }
  259. List<File> exportedLibJars(Result result) {
  260. myResult(result);
  261. return exportedLibJars;
  262. }
  263. List<Module> requiredModules(Result result) {
  264. myResult(result);
  265. return requiredModules;
  266. }
  267. private void myResult(Result result) {
  268. if ((null == result) || this != result.getModule()) {
  269. throw new IllegalArgumentException("not my result: " + result + ": " + this);
  270. }
  271. }
  272. private boolean init() {
  273. boolean cp = initClasspath();
  274. boolean mf = initManifest();
  275. if (!cp && !mf) {
  276. return false;
  277. }
  278. return initProperties() && reviewInit() && initResults();
  279. }
  280. /** read OSGI manifest.mf file XXX hacked */
  281. private boolean initManifest() {
  282. File metaInf = new File(moduleDir, "META-INF");
  283. if (!metaInf.canRead() || !metaInf.isDirectory()) {
  284. return false;
  285. }
  286. File file = new File(metaInf, "MANIFEST.MF"); // XXXFileLiteral
  287. if (!file.exists()) {
  288. return false; // ok, not OSGI
  289. }
  290. InputStream fin = null;
  291. OSGIBundle bundle = null;
  292. try {
  293. fin = new FileInputStream(file);
  294. bundle = new OSGIBundle(fin);
  295. } catch (IOException e) {
  296. messager.logException("IOException reading " + file, e);
  297. return false;
  298. } finally {
  299. Util.closeSilently(fin);
  300. }
  301. RequiredBundle[] bundles = bundle.getRequiredBundles();
  302. for (int i = 0; i < bundles.length; i++) {
  303. RequiredBundle required = bundles[i];
  304. update("src", "/" + required.name, required.text, false);
  305. }
  306. String[] libs = bundle.getClasspath();
  307. for (int i = 0; i < libs.length; i++) {
  308. update("lib", libs[i], libs[i], false);
  309. }
  310. return true;
  311. }
  312. /** read eclipse .classpath file XXX line-oriented hack */
  313. private boolean initClasspath() {
  314. // meaning testsrc directory, junit library, etc.
  315. File file = new File(moduleDir, ".classpath"); // XXXFileLiteral
  316. if (!file.exists()) {
  317. return false; // OSGI???
  318. }
  319. FileReader fin = null;
  320. try {
  321. fin = new FileReader(file);
  322. BufferedReader reader = new BufferedReader(fin);
  323. String line;
  324. XMLItem item = new XMLItem("classpathentry", new ICB());
  325. while (null != (line = reader.readLine())) {
  326. line = line.trim();
  327. // dumb - only handle comment-only lines
  328. if (!line.startsWith("<?xml") && !line.startsWith("<!--")) {
  329. item.acceptLine(line);
  330. }
  331. }
  332. return (0 < (srcDirs.size() + libJars.size()));
  333. } catch (IOException e) {
  334. messager.logException("IOException reading " + file, e);
  335. } finally {
  336. if (null != fin) {
  337. try {
  338. fin.close();
  339. } catch (IOException e) {
  340. } // ignore
  341. }
  342. }
  343. return false;
  344. }
  345. // private boolean update(String toString, String[] attributes) {
  346. // String kind = attributes[getATTSIndex("kind")];
  347. // String path = attributes[getATTSIndex("path")];
  348. // String exp = attributes[getATTSIndex("exported")];
  349. // boolean exported = ("true".equals(exp));
  350. // return update(kind, path, toString, exported);
  351. // }
  352. private boolean update(String kind, String path, String toString,
  353. boolean exported) {
  354. String libPath = null;
  355. if ("src".equals(kind)) {
  356. if (path.startsWith("/")) { // module
  357. String moduleName = path.substring(1);
  358. Module req = modules.getModule(moduleName);
  359. if (null != req) {
  360. requiredModules.add(req);
  361. return true;
  362. } else {
  363. messager.error("update unable to create required module: "
  364. + moduleName);
  365. }
  366. } else { // src dir
  367. String fullPath = getFullPath(path);
  368. File srcDir = new File(fullPath);
  369. if (srcDir.canRead() && srcDir.isDirectory()) {
  370. srcDirs.add(srcDir);
  371. return true;
  372. } else {
  373. messager.error("not a src dir: " + srcDir);
  374. }
  375. }
  376. } else if ("lib".equals(kind)) {
  377. libPath = path;
  378. } else if ("var".equals(kind)) {
  379. final String JAVA_HOME = "JAVA_HOME/";
  380. if (path.startsWith(JAVA_HOME)) {
  381. path = path.substring(JAVA_HOME.length());
  382. String home = System.getProperty("java.home");
  383. if (null != home) {
  384. libPath = Util.path(home, path);
  385. File f = new File(libPath);
  386. if (!f.exists() && home.endsWith("jre")) {
  387. f = new File(home).getParentFile();
  388. libPath = Util.path(f.getPath(), path);
  389. }
  390. }
  391. }
  392. if (null == libPath) {
  393. warnVariable(path, toString);
  394. classpathVariables.add(path);
  395. }
  396. } else if ("con".equals(kind)) {
  397. // 'special' for container pointing at AspectJ runtime...
  398. if (path.equals("org.eclipse.ajdt.core.ASPECTJRT_CONTAINER")) {
  399. classpathVariables.add("ASPECTJRT_LIB");
  400. } else {
  401. if (-1 == path.indexOf("JRE")) { // warn non-JRE containers
  402. messager.log("cannot handle con yet: " + toString);
  403. }
  404. }
  405. } else if ("out".equals(kind) || "output".equals(kind)) {
  406. // ignore output entries
  407. } else {
  408. messager.log("unrecognized kind " + kind + " in " + toString);
  409. }
  410. if (null != libPath) {
  411. File libJar = new File(libPath);
  412. if (!libJar.exists()) {
  413. libJar = new File(getFullPath(libPath));
  414. }
  415. if (libJar.canRead() && libJar.isFile()) {
  416. libJars.add(libJar);
  417. if (exported) {
  418. exportedLibJars.add(libJar);
  419. }
  420. return true;
  421. } else {
  422. messager.error("no such library jar " + libJar + " from "
  423. + toString);
  424. }
  425. }
  426. return false;
  427. }
  428. private void warnVariable(String path, String toString) {
  429. String[] known = { "JRE_LIB", "ASPECTJRT_LIB", "JRE15_LIB" };
  430. for (int i = 0; i < known.length; i++) {
  431. if (known[i].equals(path)) {
  432. return;
  433. }
  434. }
  435. messager.log("Module cannot handle var yet: " + toString);
  436. }
  437. /** @return true if any properties were read correctly */
  438. private boolean initProperties() {
  439. File file = new File(moduleDir, name + ".properties"); // XXXFileLiteral
  440. if (!Util.canReadFile(file)) {
  441. return true; // no properties to read
  442. }
  443. FileInputStream fin = null;
  444. try {
  445. fin = new FileInputStream(file);
  446. properties.load(fin);
  447. return true;
  448. } catch (IOException e) {
  449. messager.logException("IOException reading " + file, e);
  450. return false;
  451. } finally {
  452. if (null != fin) {
  453. try {
  454. fin.close();
  455. } catch (IOException e) {
  456. } // ignore
  457. }
  458. }
  459. }
  460. /**
  461. * Post-process initialization. This implementation trims java5 source dirs
  462. * if not running in a Java 5 VM.
  463. * @return true if initialization post-processing worked
  464. */
  465. protected boolean reviewInit() {
  466. try {
  467. for (ListIterator iter = srcDirs.listIterator(); iter.hasNext();) {
  468. File srcDir = (File) iter.next();
  469. String lcname = srcDir.getName().toLowerCase();
  470. if (!Util.JAVA5_VM
  471. && (Util.Constants.JAVA5_SRC.equals(lcname) || Util.Constants.JAVA5_TESTSRC
  472. .equals(lcname))) {
  473. // assume optional for pre-1.5 builds
  474. iter.remove();
  475. }
  476. }
  477. } catch (UnsupportedOperationException e) {
  478. return false; // failed XXX log also if verbose
  479. }
  480. return true;
  481. }
  482. /**
  483. * After reviewInit, setup four kinds of results.
  484. */
  485. protected boolean initResults() {
  486. return true; // results initialized lazily
  487. }
  488. /** resolve path absolutely, assuming / means base of modules dir */
  489. public String getFullPath(String path) {
  490. String fullPath;
  491. if (path.startsWith("/")) {
  492. fullPath = modules.baseDir.getAbsolutePath() + path;
  493. } else {
  494. fullPath = moduleDir.getAbsolutePath() + "/" + path;
  495. }
  496. // check for absolute paths (untested - none in our modules so far)
  497. File testFile = new File(fullPath);
  498. // System.out.println("Module.getFullPath: " + fullPath + " - " +
  499. // testFile.getAbsolutePath());
  500. if (!testFile.exists()) {
  501. testFile = new File(path);
  502. if (testFile.exists() && testFile.isAbsolute()) {
  503. fullPath = path;
  504. }
  505. }
  506. return fullPath;
  507. }
  508. class ICB implements XMLItem.ICallback {
  509. public void end(Properties attributes) {
  510. String kind = attributes.getProperty("kind");
  511. String path = attributes.getProperty("path");
  512. String exp = attributes.getProperty("exported");
  513. boolean exported = ("true".equals(exp));
  514. ByteArrayOutputStream bout = new ByteArrayOutputStream();
  515. attributes.list(new PrintStream(bout));
  516. update(kind, path, bout.toString(), exported);
  517. }
  518. }
  519. public static class XMLItem {
  520. public interface ICallback {
  521. void end(Properties attributes);
  522. }
  523. static final String START_NAME = "classpathentry";
  524. static final String ATT_STARTED = "STARTED";
  525. final ICallback callback;
  526. final StringBuffer input = new StringBuffer();
  527. final String[] attributes = new String[ATTS.length];
  528. final String targetEntity;
  529. String entityName;
  530. String attributeName;
  531. XMLItem(String targetEntity, ICallback callback) {
  532. this.callback = callback;
  533. this.targetEntity = targetEntity;
  534. reset();
  535. }
  536. private void reset() {
  537. input.setLength(0);
  538. for (int i = 0; i < attributes.length; i++) {
  539. attributes[i] = null;
  540. }
  541. entityName = null;
  542. attributeName = null;
  543. }
  544. String[] tokenize(String line) {
  545. final String DELIM = " \n\t\\<>\"=";
  546. StringTokenizer st = new StringTokenizer(line, DELIM, true);
  547. ArrayList<String> result = new ArrayList<String>();
  548. StringBuffer quote = new StringBuffer();
  549. boolean inQuote = false;
  550. while (st.hasMoreTokens()) {
  551. String s = st.nextToken();
  552. if ((1 == s.length()) && (-1 != DELIM.indexOf(s))) {
  553. if ("\"".equals(s)) { // end quote (or escaped)
  554. if (inQuote) {
  555. inQuote = false;
  556. quote.append("\"");
  557. result.add(quote.toString());
  558. quote.setLength(0);
  559. } else {
  560. quote.append("\"");
  561. inQuote = true;
  562. }
  563. } else {
  564. result.add(s);
  565. }
  566. } else { // not a delimiter
  567. if (inQuote) {
  568. quote.append(s);
  569. } else {
  570. result.add(s);
  571. }
  572. }
  573. }
  574. return (String[]) result.toArray(new String[0]);
  575. }
  576. public void acceptLine(String line) {
  577. String[] tokens = tokenize(line);
  578. for (int i = 0; i < tokens.length; i++) {
  579. next(tokens[i]);
  580. }
  581. }
  582. private Properties attributesToProperties() {
  583. Properties result = new Properties();
  584. for (int i = 0; i < attributes.length; i++) {
  585. String a = attributes[i];
  586. if (null != a) {
  587. result.setProperty(ATTS[i], a);
  588. }
  589. }
  590. return result;
  591. }
  592. void errorIfNotNull(String name, String value) {
  593. if (null != value) {
  594. error("Did not expect " + name + ": " + value);
  595. }
  596. }
  597. void errorIfNull(String name, String value) {
  598. if (null == value) {
  599. error("expected value for " + name);
  600. }
  601. }
  602. boolean activeEntity() {
  603. return targetEntity.equals(entityName);
  604. }
  605. /**
  606. * Assumes that comments and "<?xml"-style lines are removed.
  607. */
  608. public void next(String s) {
  609. if ((null == s) || (0 == s.length())) {
  610. return;
  611. }
  612. input.append(s);
  613. s = s.trim();
  614. if (0 == s.length()) {
  615. return;
  616. }
  617. if ("<".equals(s)) {
  618. errorIfNotNull("entityName", entityName);
  619. errorIfNotNull("attributeName", attributeName);
  620. } else if (">".equals(s)) {
  621. errorIfNull("entityName", entityName);
  622. if ("/".equals(attributeName)) {
  623. attributeName = null;
  624. } else {
  625. errorIfNotNull("attributeName", attributeName);
  626. }
  627. if (activeEntity()) {
  628. callback.end(attributesToProperties());
  629. }
  630. entityName = null;
  631. } else if ("=".equals(s)) {
  632. errorIfNull("entityName", entityName);
  633. errorIfNull("attributeName", attributeName);
  634. } else if (s.startsWith("\"")) {
  635. errorIfNull("entityName", entityName);
  636. errorIfNull("attributeName", attributeName);
  637. writeAttribute(attributeName, s);
  638. attributeName = null;
  639. } else {
  640. if (null == entityName) {
  641. reset();
  642. entityName = s;
  643. } else if (null == attributeName) {
  644. attributeName = s;
  645. } else {
  646. System.out
  647. .println("unknown state - not value, attribute, or entity: "
  648. + s);
  649. }
  650. }
  651. }
  652. void readAttribute(String s) {
  653. for (int i = 0; i < ATTS.length; i++) {
  654. if (s.equals(ATTS[i])) {
  655. attributes[i] = ATT_STARTED;
  656. break;
  657. }
  658. }
  659. }
  660. void writeAttribute(String name, String value) {
  661. for (int i = 0; i < ATTS.length; i++) {
  662. if (name.equals(ATTS[i])) {
  663. if (!value.startsWith("\"") || !value.endsWith("\"")) {
  664. error("bad attribute value: " + value);
  665. }
  666. value = value.substring(1, value.length() - 1);
  667. attributes[i] = value;
  668. return;
  669. }
  670. }
  671. }
  672. void error(String s) {
  673. throw new Error(s + " at input " + input);
  674. }
  675. }
  676. }