You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

Module.java 25KB

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