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-2007 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: package org.netbeans.modules.lexer.gen;
043:
044: import java.lang.reflect.Field;
045: import java.io.IOException;
046: import java.util.ArrayList;
047: import java.util.Arrays;
048: import java.util.Map;
049: import java.util.HashMap;
050: import java.util.Set;
051: import java.util.List;
052:
053: import org.netbeans.api.lexer.TokenId;
054: import org.netbeans.spi.lexer.util.LexerUtilities;
055: import java.util.Iterator;
056: import org.netbeans.modules.lexer.gen.util.LexerGenUtilities;
057:
058: /**
059: * Generates Language class source by using xml file
060: * with the language description. Descendants of this class
061: * typically read some portion of the information
062: * (e.g. integer token identifications) from the classes
063: * generated by particular lexer.
064: *
065: * @author Miloslav Metelka
066: * @version 1.00
067: */
068:
069: public class LanguageGenerator {
070:
071: /** Map containing collected imports */
072: private Map imports;
073:
074: public LanguageGenerator() {
075: this .imports = new HashMap();
076: }
077:
078: public String createSource(LanguageData data) {
079: // Check consistency of language data
080: data.check();
081:
082: StringBuffer sb = new StringBuffer();
083:
084: // Class header
085: appendClassDeclaration(sb, data);
086:
087: // Class start
088: appendClassStart(sb, data);
089:
090: addToImports("org.netbeans.api.lexer.TokenId");
091:
092: // TokenId intIds declarations
093: for (Iterator idsIterator = data.getSortedIds(
094: LanguageData.IDS_INT_ID_COMPARATOR).iterator(); idsIterator
095: .hasNext();) {
096: MutableTokenId id = (MutableTokenId) idsIterator.next();
097: LexerGenUtilities.appendSpaces(sb, 4);
098: sb.append("public static final int ");
099: sb.append(id.getIntIdFieldName());
100: sb.append(" = ");
101: sb.append(id.getIntId());
102: sb.append(";\n");
103: }
104:
105: sb.append("\n\n");
106:
107: for (Iterator idsIterator = data.getSortedIds(
108: LanguageData.IDS_FIELD_NAME_COMPARATOR).iterator(); idsIterator
109: .hasNext();) {
110: MutableTokenId id = (MutableTokenId) idsIterator.next();
111: LexerGenUtilities.appendSpaces(sb, 4);
112: sb.append("public static final TokenId ");
113: sb.append(id.getFieldName());
114: sb.append(" = new TokenId(\"");
115: LexerUtilities.appendToSource(sb, id.getName());
116: sb.append("\", ");
117: sb.append(id.getIntIdFieldName());
118:
119: // Append categories
120: List catList = id.getCategoryNames();
121: int catCnt = catList.size();
122: if (catCnt > 0) {
123: sb.append(", new String[]{\"");
124: for (int j = 0; j < catCnt; j++) {
125: if (j > 0) {
126: sb.append(", \"");
127: }
128: LexerUtilities.appendToSource(sb, (String) catList
129: .get(j));
130: sb.append('"');
131: }
132: sb.append('}');
133: }
134:
135: // Append texts
136: List sampleTexts = id.getSampleTexts();
137: int sampleTextsLength = sampleTexts.size();
138: if (sampleTextsLength > 0) {
139: if (catCnt == 0) { // need to appen null instead of cats
140: sb.append(", null");
141: }
142:
143: addToImports("org.netbeans.spi.lexer.MatcherFactory");
144: sb.append(", MatcherFactory.create");
145: String sampleTextCheck = id.getSampleTextCheck();
146: if (sampleTextsLength == 1) { // exactly one sample text
147: if ("none".equals(sampleTextCheck)) {
148: sb.append("NoCheckMatcher");
149: } else if ("length".equals(sampleTextCheck)) {
150: sb.append("LengthCheckMatcher");
151: } else {
152: sb.append("TextCheckMatcher");
153: }
154:
155: sb.append("(\"");
156: LexerUtilities.appendToSource(sb,
157: (String) sampleTexts.get(0));
158: sb.append('"');
159:
160: } else { // more than one sample text
161: sb.append("MultiMatcher(new String[] {");
162:
163: for (Iterator it = sampleTexts.iterator(); it
164: .hasNext();) {
165: sb.append('"');
166: LexerUtilities.appendToSource(sb, (String) it
167: .next());
168: sb.append('"');
169: if (it.hasNext()) {
170: sb.append(", ");
171: }
172: }
173: sb.append("}");
174: }
175: sb.append(")");
176:
177: }
178:
179: sb.append(");");
180:
181: String comment = id.getComment();
182: if (comment != null) {
183: sb.append(" // ");
184: sb.append(comment);
185: }
186:
187: sb.append('\n');
188: }
189:
190: // Constructor
191: sb.append('\n');
192: appendClassConstructor(sb, data);
193:
194: // Lexer creation
195: LexerGenUtilities.appendSpaces(sb, 4);
196: sb.append("public ");
197: sb.append(addToImports("org.netbeans.api.lexer.Lexer"));
198: sb.append(" createLexer() {\n");
199:
200: appendLexerMethodBody(sb, data);
201: LexerGenUtilities.appendSpaces(sb, 4);
202: sb.append("}\n");
203:
204: // Class end
205: appendClassEnd(sb, data);
206:
207: sb.append("\n}\n");
208:
209: // Now header containing package stmt and imports is generated
210: StringBuffer hsb = new StringBuffer();
211: // [PENDING] ? License
212: // Possible package stmt
213: String langClassPkgName = getLanguageClassPkgName(data);
214: if (!"".equals(langClassPkgName)) {
215: hsb.append("\npackage ").append(langClassPkgName).append(
216: ";\n");
217: }
218:
219: // Add imports
220: hsb.append('\n');
221: appendImports(hsb, data);
222: hsb.append('\n');
223:
224: // Insert package and imports to begining of the generated class
225: sb.insert(0, hsb.toString());
226:
227: return sb.toString();
228: }
229:
230: private String getLanguageClassNameWithoutPkg(LanguageData data) {
231: return addToImports(data.getLanguageClassName());
232: }
233:
234: private String getLanguageClassPkgName(LanguageData data) {
235: return LexerGenUtilities.splitClassName(data
236: .getLanguageClassName())[0];
237: }
238:
239: protected void appendClassDeclaration(StringBuffer sb,
240: LanguageData data) {
241: sb.append("public class ");
242: sb.append(getLanguageClassNameWithoutPkg(data));
243: sb.append(" extends ");
244: sb
245: .append(addToImports("org.netbeans.spi.lexer.AbstractLanguage"));
246: sb.append(" {\n\n");
247: }
248:
249: protected void appendClassStart(StringBuffer sb, LanguageData data) {
250:
251: // Append static get() method
252: String langClassNameWithoutPkg = getLanguageClassNameWithoutPkg(data);
253: LexerGenUtilities.appendSpaces(sb, 4);
254: sb
255: .append("/** Lazily initialized singleton instance of this language. */\n");
256: LexerGenUtilities.appendSpaces(sb, 4);
257: sb.append("private static ").append(langClassNameWithoutPkg);
258: sb.append(" INSTANCE;\n\n");
259: LexerGenUtilities.appendSpaces(sb, 4);
260: sb
261: .append("/** @return singleton instance of this language. */\n");
262: LexerGenUtilities.appendSpaces(sb, 4);
263: sb.append("public static synchronized ").append(
264: langClassNameWithoutPkg);
265: sb.append(" get() {\n");
266: LexerGenUtilities.appendSpaces(sb, 8);
267: sb.append("if (INSTANCE == null)\n");
268: LexerGenUtilities.appendSpaces(sb, 12);
269: sb.append("INSTANCE = new ").append(langClassNameWithoutPkg);
270: sb.append("();\n\n");
271: LexerGenUtilities.appendSpaces(sb, 8);
272: sb.append("return INSTANCE;\n");
273: LexerGenUtilities.appendSpaces(sb, 4);
274: sb.append("}\n\n");
275: }
276:
277: protected void appendClassConstructor(StringBuffer sb,
278: LanguageData data) {
279: LexerGenUtilities.appendSpaces(sb, 4);
280: sb.append(getLanguageClassNameWithoutPkg(data));
281: sb.append("() {\n");
282: LexerGenUtilities.appendSpaces(sb, 4);
283: sb.append("}\n\n");
284: }
285:
286: protected void appendClassEnd(StringBuffer sb, LanguageData data) {
287: }
288:
289: protected void appendLexerMethodBody(StringBuffer sb,
290: LanguageData data) {
291: String lexerClassName = data.getLexerClassName();
292: addToImports(lexerClassName);
293: LexerGenUtilities.appendSpaces(sb, 8);
294: sb.append("return new ");
295: sb.append(addToImports(lexerClassName));
296: sb.append("();\n");
297: }
298:
299: /**
300: * Add the class to imports section. This method can be used
301: * anytime even during the class source generation
302: * because the imports are added to the generated source
303: * at the end of the whole process.
304: * @param className full name of the class including package name.
305: * @return the short name of the class name (without package).
306: */
307: protected final String addToImports(String className) {
308: String[] pkgCls = (String[]) imports.get(className);
309: if (pkgCls == null) {
310: pkgCls = LexerGenUtilities.splitClassName(className);
311: imports.put(className, pkgCls);
312: }
313: return pkgCls[1];
314: }
315:
316: private void appendImports(StringBuffer sb, LanguageData data) {
317: String langClassPkgName = getLanguageClassPkgName(data);
318: Set iSet = imports.keySet();
319: String[] importClasses = new String[iSet.size()];
320: iSet.toArray(importClasses);
321: Arrays.sort(importClasses);
322: for (int i = 0; i < importClasses.length; i++) {
323: String className = importClasses[i];
324: String[] pkgCls = (String[]) imports.get(className);
325: if (!"".equals(pkgCls[0]) && !"java.lang".equals(pkgCls[0])
326: && !pkgCls[0].equals(langClassPkgName) // differs from package stmt
327: ) {
328: sb.append("import ").append(className).append(";\n");
329: }
330: }
331: }
332:
333: }
|