c.s.tools.ide.analysis.modernize/src/com/sun/tools/ide/analysis/modernize/impl/YamlParser.java
2 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
4 * Copyright (c) 2017 Oracle and/or its affiliates. All rights reserved.
6 * Oracle and Java are registered trademarks of Oracle and/or its affiliates.
7 * Other names may be trademarks of their respective owners.
9 * The contents of this file are subject to the terms of either the GNU
10 * General Public License Version 2 only ("GPL") or the Common
11 * Development and Distribution License("CDDL") (collectively, the
12 * "License"). You may not use this file except in compliance with the
13 * License. You can obtain a copy of the License at
14 * http://www.netbeans.org/cddl-gplv2.html
15 * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
16 * specific language governing permissions and limitations under the
17 * License. When distributing the software, include this License Header
18 * Notice in each file and include the License file at
19 * nbbuild/licenses/CDDL-GPL-2-CP. Oracle designates this
20 * particular file as subject to the "Classpath" exception as provided
21 * by Oracle in the GPL Version 2 section of the License file that
22 * accompanied this code. If applicable, add the following below the
23 * License Header, with the fields enclosed by brackets [] replaced by
24 * your own identifying information:
25 * "Portions Copyrighted [year] [name of copyright owner]"
27 * If you wish your version of this file to be governed by only the CDDL
28 * or only the GPL Version 2, indicate your decision by adding
29 * "[Contributor] elects to include this software in this distribution
30 * under the [CDDL or GPL Version 2] license." If you do not indicate a
31 * single choice of license, a recipient has the option to distribute
32 * your version of this file under either the CDDL, the GPL Version 2 or
33 * to extend the choice of license to its licensees as provided above.
34 * However, if you add GPL Version 2 code and therefore, elected the GPL
35 * Version 2 license, then the option applies only if the new code is
36 * made subject to such option by the copyright holder.
38 * Contributor(s): Ilia Gromov
40 package com.sun.tools.ide.analysis.modernize.impl;
42 import static com.sun.tools.ide.analysis.modernize.impl.YamlParser.Diagnostics.UNDEFINED;
43 import java.util.ArrayList;
44 import java.util.Collections;
45 import java.util.List;
46 import java.util.StringTokenizer;
47 import java.util.logging.Level;
53 public class YamlParser {
54 // TODO check for no-replacements case
56 private static final YamlParser INSTANCE = new YamlParser();
58 public static YamlParser getDefault() {
62 private static final String OPEN_TAG = "---"; //NOI18N
63 private static final String CLOSE_TAG = "..."; //NOI18N
65 private static final String MAIN_SOURCE_FILE = "MainSourceFile"; //NOI18N
66 private static final String CHECK_NAME = "CheckName"; //NOI18N
67 private static final String LEVEL = "Level"; //NOI18N
68 private static final String MESSAGE_FILE_PATH = "MessageFilePath"; //NOI18N
69 private static final String MESSAGE_FILE_OFFSET = "MessageFileOffset"; //NOI18N
70 private static final String MESSAGE = "Message"; //NOI18N
71 private static final String REPLACEMENTS = "Replacements"; //NOI18N
72 private static final String DIAGNOSTICS = "Diagnostics"; //NOI18N
74 private static final String FILE_PATH = "FilePath"; //NOI18N
75 private static final String OFFSET = "Offset"; //NOI18N
76 private static final String LENGTH = "Length"; //NOI18N
77 private static final String REPLACEMENT_TEXT = "ReplacementText"; //NOI18N
81 // MainSourceFile: /home/ilia/NetBeansProjects/CppApplication_46/main.cpp
83 // - FilePath: /home/ilia/NetBeansProjects/CppApplication_46/newfile.h
86 // ReplacementText: nullptr
87 // - FilePath: /home/ilia/NetBeansProjects/CppApplication_46/newfile1.h
90 // ReplacementText: nullptr
91 // - FilePath: /home/ilia/NetBeansProjects/CppApplication_46/main.cpp
94 // ReplacementText: nullptr
100 // CheckName: misc-macro-parentheses
102 // - FilePath: /media/SSD_/code/zdoom/main_1.cpp
105 // ReplacementText: '('
106 // - FilePath: /media/SSD_/code/zdoom/main_1.cpp
109 // ReplacementText: ')'
111 // CheckName: misc-macro-parentheses
113 // - FilePath: /media/SSD_/code/zdoom/main_1.cpp
116 // ReplacementText: '('
117 // - FilePath: /media/SSD_/code/zdoom/main_1.cpp
120 // ReplacementText: ')'
122 public List<Diagnostics> parseYaml(String output) {
124 if (output.isEmpty()) {
125 // No warnings in header = no 'Main Source File' element
126 return Collections.EMPTY_LIST;
128 StringTokenizer st = new StringTokenizer(output, "\n"); //NOI18N
132 line = st.nextToken();
134 line = st.nextToken();
135 String mainSourceFile = getValue(MAIN_SOURCE_FILE, line);
138 line = st.nextToken();
139 ClangTidyVersion version = line.contains(REPLACEMENTS)
140 ? ClangTidyVersion.OLD_FORMAT
141 : ClangTidyVersion.NEW_FORMAT;
143 List<Diagnostics> list = version.parser.parse(st);
146 } catch (Exception ex) {
147 ModernizeErrorProvider.LOG.log(Level.INFO, "Can't parse output line");
148 return Collections.EMPTY_LIST;
152 private static List<Replacement> parseReplacements(StringTokenizer st) {
153 List<Replacement> replacements = new ArrayList<Replacement>();
157 line = st.nextToken();
159 if (!line.contains(REPLACEMENTS)) {
163 while (st.hasMoreTokens()) {
164 line = st.nextToken();
166 if (line.equals(CLOSE_TAG) || !line.contains(FILE_PATH)) {
170 String filePath = getValue(FILE_PATH, line);
172 line = st.nextToken();
173 int offset = Integer.parseInt(getValue(OFFSET, line));
175 line = st.nextToken();
176 int length = Integer.parseInt(getValue(LENGTH, line));
178 line = st.nextToken();
179 String replacementText = getValue(REPLACEMENT_TEXT, line);
180 while (replacementText == null && st.hasMoreElements()) {
181 line = line + "\n" + st.nextElement(); //NOI18N
182 replacementText = getValue(REPLACEMENT_TEXT, line);
185 Replacement replacement = new Replacement(filePath, offset, length, replacementText);
186 replacements.add(replacement);
192 private static String getValue(String key, String line) {
193 int keyStart = line.indexOf(key);
194 assert keyStart != -1;
195 int keyEnd = keyStart + key.length() + 1;
196 String trimmed = line.substring(keyEnd).trim();
197 boolean isTextValue = trimmed.charAt(0) == '\'';
199 if (trimmed.length() == 1) {
203 } else if (trimmed.charAt(trimmed.length() - 1) == '\'') {
204 trimmed = trimmed.substring(1, trimmed.length() - 1);
212 public static class TranslationUnitDiagnostics {
214 private final String mainSourceFilePath;
215 private final String context;
216 private final List<Diagnostics> diags;
218 public TranslationUnitDiagnostics(String mainSourceFilePath, String context, List<Diagnostics> diags) {
219 this.mainSourceFilePath = mainSourceFilePath;
220 this.context = context;
224 public String getMainSourceFilePath() {
225 return mainSourceFilePath;
228 public String getContext() {
232 public List<Diagnostics> getDiags() {
237 public static class Diagnostics {
239 public static final String UNDEFINED = "undefined"; // NOI18N
245 private final String checkName;
246 private final Diagnostics.Level level;
247 private final String messageFilePath;
248 private final int messageFileOffset;
249 private final String message;
250 private final List<Replacement> replacements;
252 public Diagnostics(String checkName, Diagnostics.Level level, String messageFilePath, int messageFileOffset, String message, List<Replacement> replacements) {
253 this.checkName = checkName;
255 this.messageFilePath = messageFilePath;
256 this.messageFileOffset = messageFileOffset;
257 this.message = message;
258 this.replacements = replacements;
261 public String getCheckName() {
265 public Level getLevel() {
269 public String getMessageFilePath() {
270 return messageFilePath;
273 public int getMessageFileOffset() {
274 return messageFileOffset;
277 public String getMessage() {
281 public List<Replacement> getReplacements() {
286 public static class Replacement {
288 public final String filePath;
289 public final int offset;
290 public final int length;
291 public final String replacementText;
293 public Replacement(String FilePath, int offset, int length, String replacementText) {
294 this.filePath = FilePath;
295 this.offset = offset;
296 this.length = length;
297 this.replacementText = replacementText;
301 private static enum ClangTidyVersion {
302 NEW_FORMAT(new NewFormatParser()),
303 OLD_FORMAT(new OldFormatParser());
305 public final TidyParser parser;
307 private ClangTidyVersion(TidyParser parser) {
308 this.parser = parser;
312 private static abstract class TidyParser {
314 abstract List<Diagnostics> parse(StringTokenizer st);
317 private static class NewFormatParser extends TidyParser {
320 public List<Diagnostics> parse(StringTokenizer st) {
321 List<Diagnostics> list = new ArrayList<Diagnostics>();
324 while (st.hasMoreElements()) {
325 line = st.nextToken();
326 String checkName = getValue(CHECK_NAME, line);
328 line = st.nextToken();
329 Diagnostics.Level level = Diagnostics.Level.valueOf(getValue(LEVEL, line).toLowerCase());
331 line = st.nextToken();
332 String messageFilePath = getValue(MESSAGE_FILE_PATH, line);
334 line = st.nextToken();
335 int messageFileOffset = Integer.parseInt(getValue(MESSAGE_FILE_OFFSET, line));
337 line = st.nextToken();
338 String message = getValue(MESSAGE, line);
340 List<Replacement> replacements = parseReplacements(st);
342 list.add(new Diagnostics(checkName, level, messageFilePath, messageFileOffset, message, replacements));
349 private static class OldFormatParser extends TidyParser {
352 public List<Diagnostics> parse(StringTokenizer st) {
353 List<Replacement> parseReplacements = parseReplacements(st);
354 return Collections.singletonList(new Diagnostics(UNDEFINED, Diagnostics.Level.warning, "", -1, "", parseReplacements)); //NOI18N