001: /*
002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
003: *
004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
005: *
006: * The contents of this file are subject to the terms of either the GNU
007: * General Public License Version 2 only ("GPL") or the Common
008: * Development and Distribution License("CDDL") (collectively, the
009: * "License"). You may not use this file except in compliance with the
010: * License. You can obtain a copy of the License at
011: * http://www.netbeans.org/cddl-gplv2.html
012: * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
013: * specific language governing permissions and limitations under the
014: * License. When distributing the software, include this License Header
015: * Notice in each file and include the License file at
016: * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
017: * particular file as subject to the "Classpath" exception as provided
018: * by Sun in the GPL Version 2 section of the License file that
019: * accompanied this code. If applicable, add the following below the
020: * License Header, with the fields enclosed by brackets [] replaced by
021: * your own identifying information:
022: * "Portions Copyrighted [year] [name of copyright owner]"
023: *
024: * Contributor(s):
025: *
026: * The Original Software is NetBeans. The Initial Developer of the Original
027: * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
028: * Microsystems, Inc. All Rights Reserved.
029: *
030: * If you wish your version of this file to be governed by only the CDDL
031: * or only the GPL Version 2, indicate your decision by adding
032: * "[Contributor] elects to include this software in this distribution
033: * under the [CDDL or GPL Version 2] license." If you do not indicate a
034: * single choice of license, a recipient has the option to distribute
035: * your version of this file under either the CDDL, the GPL Version 2 or
036: * to extend the choice of license to its licensees as provided above.
037: * However, if you add GPL Version 2 code and therefore, elected the GPL
038: * Version 2 license, then the option applies only if the new code is
039: * made subject to such option by the copyright holder.
040: */
041:
042: /*
043: * CommentingPreProcessor.java
044: *
045: * Created on August 12, 2005, 9:37 AM
046: */
047: package org.netbeans.mobility.antext.preprocessor;
048:
049: import java.io.FileReader;
050: import java.io.OutputStreamWriter;
051: import java.io.Reader;
052: import java.io.Writer;
053:
054: import java.io.IOException;
055: import java.io.StringWriter;
056: import java.util.*;
057:
058: /**
059: * @author Adam Sotona
060: */
061: public final class CommentingPreProcessor implements Runnable {
062:
063: static final String DEFAULT_COMMENT = "//# "; //NOI18N
064:
065: public static interface AbilitiesEvaluator {
066:
067: public boolean isAbilityDefined(String abilityName);
068:
069: public String getAbilityValue(String abilityName);
070:
071: public void requestDefineAbility(String abilityName,
072: String value);
073:
074: public void requestUndefineAbility(String abilityName);
075: }
076:
077: public static interface Source {
078:
079: public Reader createReader() throws IOException;
080:
081: }
082:
083: public static interface Destination {
084:
085: public Writer createWriter(boolean validOutput)
086: throws IOException;
087:
088: public void doInsert(int line, String s) throws IOException;
089:
090: public void doRemove(int line, int column, int length)
091: throws IOException;
092:
093: }
094:
095: private final Source src;
096: private final Destination dest;
097: private final Stack<PPBlockInfo> stack;
098: private final AbilitiesEvaluator eval;
099: private final ArrayList<PPBlockInfo> blockList;
100: private final ArrayList<PPLine> lines;
101: private PPLine line;
102: private LineParser lp;
103:
104: protected PPBlockInfo stackTop;
105:
106: public CommentingPreProcessor(Source src, Destination dest,
107: String abilities) {
108: this (src, dest, decodeAbilitiesMap(abilities));
109: }
110:
111: public CommentingPreProcessor(Source src, Destination dest,
112: Map<String, String> abilities) {
113: this .src = src;
114: this .dest = dest;
115: this .blockList = new ArrayList<PPBlockInfo>();
116: this .lines = new ArrayList<PPLine>();
117: this .stack = new Stack<PPBlockInfo>();
118: this .stackTop = null;
119: this .eval = new MapEvaluator(abilities);
120: this .line = null;
121: }
122:
123: public ArrayList<PPLine> getLines() {
124: return this .lines;
125: }
126:
127: public ArrayList<PPBlockInfo> getBlockList() {
128: return this .blockList;
129: }
130:
131: public void run() {
132: Exception e = null;
133: Reader r = null;
134: try {
135: r = src.createReader();
136: parse(r);
137: } catch (Exception ex) {
138: while (lp.hasMoreLines())
139: lines.add(lp.nextLine()); //this reads the rest of lines in case of critical error
140: e = ex;
141: }
142: Writer w = null;
143: if (dest != null)
144: try {
145: w = dest.createWriter(e == null && !hasErrors()
146: && conditionIsTrue());
147: writeOutput(w);
148: } catch (IOException ioe) {
149: throw new PreprocessorException(
150: "IOException during write", ioe); //NOI18N
151: } finally {
152: if (r != null)
153: try {
154: r.close();
155: } catch (IOException ioe) {
156: throw new PreprocessorException(
157: "IOException during source reader close",
158: ioe); //NOI18N
159: }
160: if (w != null)
161: try {
162: w.close();
163: } catch (IOException ioe) {
164: throw new PreprocessorException(
165: "IOException during detination writer close/flush",
166: ioe); //NOI18N
167: }
168: }
169: if (e != null) {
170: if (e instanceof PreprocessorException) {
171: e.fillInStackTrace();
172: throw (PreprocessorException) e;
173: }
174: throw new PreprocessorException(
175: "Critical Preprocessor Exception", e); //NOI18N
176: }
177: }
178:
179: private void parse(final Reader r) {
180: lp = new LineParser(r, eval);
181: while (lp.hasMoreLines()) {
182: line = lp.nextLine();
183: lines.add(line);
184: final boolean reduceDebug = debugOnTop();
185: switch (line.getType()) {
186: case PPLine.IF:
187: case PPLine.CONDITION:
188: case PPLine.IFDEF:
189: case PPLine.IFNDEF:
190: case PPLine.OLDIF:
191: case PPLine.MDEBUG:
192: case PPLine.DEBUG:
193: push(new PPBlockInfo(stackTop, line, line.hasValue(),
194: line.getValue(), null));
195: line.setBlock(stackTop);
196: break;
197: case PPLine.ENDIF:
198: line.setBlock(stackTop);
199: if (stackTop != null
200: && stackTop.getType() != PPLine.OLDIF
201: && stackTop.getType() != PPLine.MDEBUG)
202: pop(true);
203: else
204: line.addError("ERR_redundant_endif"); //NOI18N
205: break;
206: case PPLine.OLDENDIF:
207: line.setBlock(stackTop);
208: if (stackTop != null
209: && stackTop.getType() == PPLine.OLDIF)
210: pop(true);
211: else
212: line.addError("ERR_redundant_old_block_end"); //NOI18N
213: break;
214: case PPLine.ELSE:
215: case PPLine.ELIF:
216: case PPLine.ELIFDEF:
217: case PPLine.ELIFNDEF:
218: if (stackTop != null
219: && stackTop.getType() != PPLine.OLDIF
220: && stackTop.getType() != PPLine.MDEBUG
221: && stackTop.getType() != PPLine.ELSE) {
222: final PPBlockInfo ifChainAncestor = stackTop;
223: pop(false);
224: push(new PPBlockInfo(stackTop, line, line
225: .hasValue(), line.getValue(),
226: ifChainAncestor));
227: } else
228: line.addError("ERR_redundant_else"); //NOI18N
229: line.setBlock(stackTop);
230: break;
231: case PPLine.ENDDEBUG:
232: line.setBlock(stackTop);
233: if (stackTop != null
234: && stackTop.getType() == PPLine.MDEBUG)
235: pop(true);
236: else
237: line.addError("ERR_redundant_enddebug"); //NOI18N
238: break;
239: default:
240: line.setBlock(stackTop);
241: }
242:
243: if (reduceDebug && debugOnTop())
244: pop(true); //debug should have short live on stack
245: }
246: while (stackTop != null) {
247: if (stackTop.getType() != PPLine.CONDITION)
248: stackTop.addError("ERR_unterminated_block"); //NOI18N
249: pop(false);
250: }
251: }
252:
253: private void writeOutput(final Writer w) throws IOException {
254: for (final PPLine l : lines) {
255: final Iterator<PPToken> tk = l.getTokens().iterator();
256: final PPBlockInfo b = l.getBlock();
257: if (b == null || b.isToBeCommented()) {
258: if (b == null || b.isActive()) {
259: if (l.getType() == PPLine.COMMENTED) {
260: final PPToken token = tk.next();
261: if (b == null && "//--".equals(token.getText())) { //NOI18N
262: if (w != null)
263: w.write(token.getText()); // do not remove //-- comments when outside a PP block
264: } else {
265: dest.doRemove(token.getLine(), token
266: .getColumn(), token.getText()
267: .length());
268: }
269: }
270: } else {
271: if (l.getType() == PPLine.UNCOMMENTED) {
272: dest.doInsert(l.getLineNumber(),
273: DEFAULT_COMMENT);
274: if (w != null)
275: w.write(DEFAULT_COMMENT);
276: }
277: }
278: }
279: if (w != null) {
280: while (tk.hasNext()) {
281: final PPToken token = tk.next();
282: w.write(token.getPadding());
283: w.write(token.getText());
284: }
285: }
286: }
287: }
288:
289: private void push(final PPBlockInfo b) {
290: blockList.add(b);
291: stack.push(stackTop);
292: stackTop = b;
293: }
294:
295: private void pop(final boolean hasFooter) {
296: if (stack.isEmpty()) {
297: stackTop = null;
298: } else {
299: stackTop.setEndLine(line.getLineNumber()
300: - (hasFooter ? 0 : 1));
301: stackTop.setHasFooter(hasFooter);
302: stackTop = stack.pop();
303: if (debugOnTop())
304: pop(false); //reduction of all remaining debugs from top of the stack
305: }
306: }
307:
308: private boolean hasErrors() {
309: for (PPLine ppl : lines)
310: if (ppl.hasErrors())
311: return true;
312: return false;
313: }
314:
315: private boolean conditionIsTrue() {
316: if (lines.size() == 0)
317: return true;
318: final PPLine l = lines.get(0);
319: return l.getType() != PPLine.CONDITION || !l.hasValue()
320: || l.getValue();
321: }
322:
323: private boolean debugOnTop() {
324: return stackTop != null && stackTop.getType() == PPLine.DEBUG;
325: }
326:
327: public static String encodeAbilitiesMap(
328: final Map<String, String> abilities) {
329: final StringBuffer sb = new StringBuffer();
330: if (abilities != null) {
331: for (final Map.Entry<String, String> me : abilities
332: .entrySet()) {
333: if (sb.length() > 0)
334: sb.append(',');
335: sb.append(me.getKey());
336: final String val = me.getValue();
337: if (val != null) {
338: sb.append('=');
339: for (int i = 0; i < val.length(); i++) {
340: final char c = val.charAt(i);
341: if (c == '\\' || c == ',')
342: sb.append('\\');
343: sb.append(c);
344: }
345: }
346: }
347: }
348: return sb.toString();
349: }
350:
351: public static Map<String, String> decodeAbilitiesMap(
352: final String abilities) {
353: final HashMap<String, String> map = new HashMap<String, String>();
354: if (abilities != null) {
355: final StringBuffer sb = new StringBuffer();
356: boolean backslash = false;
357: String key = null;
358: for (int i = 0; i < abilities.length(); i++) {
359: final char c = abilities.charAt(i);
360: if (key == null) {
361: if (c == '=') {
362: key = sb.toString();
363: sb.setLength(0);
364: } else if (c == ',') {
365: map.put(sb.toString(), null);
366: sb.setLength(0);
367: } else {
368: sb.append(c);
369: }
370: } else {
371: if (backslash) {
372: if (c != '\\' && c != ',')
373: sb.append('\\');
374: sb.append(c);
375: backslash = false;
376: } else if (c == '\\') {
377: backslash = true;
378: } else if (c == ',') {
379: map.put(key, sb.toString());
380: key = null;
381: sb.setLength(0);
382: } else {
383: sb.append(c);
384: }
385: }
386: }
387: if (backslash)
388: sb.append('\\');
389: if (key == null)
390: map.put(sb.toString(), null);
391: else
392: map.put(key, sb.toString());
393: }
394: map.remove(""); //NOI18N
395: return map;
396: }
397:
398: private class MapEvaluator extends HashMap<String, String>
399: implements AbilitiesEvaluator {
400:
401: public MapEvaluator() {
402: super ();
403: }
404:
405: public MapEvaluator(Map<String, String> m) {
406: super (m);
407: }
408:
409: public boolean isAbilityDefined(final String abilityName) {
410: return containsKey(abilityName);
411: }
412:
413: public String getAbilityValue(final String abilityName) {
414: return get(abilityName);
415: }
416:
417: public void requestDefineAbility(final String abilityName,
418: final String value) {
419: if ((stackTop == null || !stackTop.isToBeCommented() || stackTop
420: .isActive())
421: && !containsKey(abilityName))
422: put(abilityName, value);
423: }
424:
425: public void requestUndefineAbility(final String abilityName) {
426: if (stackTop == null || !stackTop.isToBeCommented()
427: || stackTop.isActive())
428: remove(abilityName);
429: }
430: }
431:
432: public static void main(final String args[]) throws Exception {
433: new CommentingPreProcessor(new Source() {
434: public Reader createReader() throws IOException {
435: return new FileReader(args[0]);
436: }
437: }, new Destination() {
438: public void doInsert(int line, @SuppressWarnings("unused")
439: String s) throws IOException {
440: System.err.print(String.valueOf(line) + "+, ");
441: }
442:
443: public void doRemove(int line, @SuppressWarnings("unused")
444: int column, @SuppressWarnings("unused")
445: int length) throws IOException {
446: System.err.print(String.valueOf(line) + "-, ");
447: }
448:
449: public Writer createWriter(boolean validOutput)
450: throws IOException {
451: return new StringWriter();
452: }
453:
454: }, Collections.singletonMap("aaa", (String) null)).run();
455: }
456: }
|