View Javadoc
1   /*
2    * Copyright (c) 2020 bahlef.
3    * All rights reserved. This program and the accompanying materials
4    * are made available under the terms of the Eclipse Public License v2.0
5    * which accompanies this distribution, and is available at
6    * http://www.eclipse.org/legal/epl-v20.html
7    * Contributors:
8    * bahlef - initial API and implementation and/or initial documentation
9    */
10  package de.funfried.netbeans.plugins.external.formatter.eclipse.xml;
11  
12  import java.io.File;
13  import java.io.IOException;
14  import java.io.StringReader;
15  import java.net.URL;
16  import java.nio.charset.StandardCharsets;
17  import java.util.ArrayList;
18  import java.util.HashMap;
19  import java.util.List;
20  import java.util.Map;
21  import java.util.Objects;
22  import java.util.logging.Level;
23  import java.util.logging.Logger;
24  
25  import org.apache.commons.io.FileUtils;
26  import org.apache.commons.io.IOUtils;
27  import org.apache.commons.lang3.StringUtils;
28  import org.netbeans.api.annotations.common.NonNull;
29  import org.openide.xml.XMLUtil;
30  import org.w3c.dom.Document;
31  import org.w3c.dom.Element;
32  import org.w3c.dom.NamedNodeMap;
33  import org.w3c.dom.Node;
34  import org.w3c.dom.NodeList;
35  import org.xml.sax.InputSource;
36  import org.xml.sax.SAXException;
37  
38  import de.funfried.netbeans.plugins.external.formatter.exceptions.ConfigReadException;
39  import de.funfried.netbeans.plugins.external.formatter.exceptions.ProfileNotFoundException;
40  
41  /**
42   * This class reads a config file for Eclipse code formatter.
43   *
44   * @author bahlef
45   */
46  public class ConfigReader {
47  	private static final Logger log = Logger.getLogger(ConfigReader.class.getName());
48  
49  	public static final String ATTRIBUTE_PROFILE_KIND = "kind";
50  
51  	public static final String ATTRIBUTE_PROFILE_NAME = "name";
52  
53  	public static final String ATTRIBUTE_SETTING_ID = "id";
54  
55  	public static final String ATTRIBUTE_SETTING_VALUE = "value";
56  
57  	public static final String TAG_NAME_PROFILES = "profiles";
58  
59  	public static final String TAG_NAME_PROFILE = "profile";
60  
61  	public static final String TAG_NAME_SETTING = "setting";
62  
63  	public static final String PROFILE_KIND = "CodeFormatterProfile";
64  
65  	/**
66  	 * Reads the content of the given file path and returns it as a {@link String}.
67  	 *
68  	 * @param filePath a file path
69  	 *
70  	 * @return the content of the file at the fiven {@code filePath}
71  	 *
72  	 * @throws IOException if there is an issue accessing the file at the given path
73  	 */
74  	@NonNull
75  	public static String readContentFromFilePath(String filePath) throws IOException {
76  		try {
77  			URL url = new URL(filePath);
78  
79  			return IOUtils.toString(url.openStream(), StandardCharsets.UTF_8);
80  		} catch (IOException ex) {
81  			log.log(Level.FINEST, "Could not read file via URL, fallback to local file reading", ex);
82  
83  			return FileUtils.readFileToString(new File(filePath), StandardCharsets.UTF_8);
84  		}
85  	}
86  
87  	/**
88  	 * Parses and returns the key/value pairs from the given {@code fileContent} for the given {@code profileName} as a {@link Map}.
89  	 *
90  	 * @param fileContent the file content to parse
91  	 * @param profileName the profile name for which to get the settings
92  	 *
93  	 * @return a {@link Map} within all the configuration paramters of the given {@code profileName} read from the given {@code fileContent},
94  	 *         or throws an exception if there's a problem reading the input, e.g.: invalid XML.
95  	 *
96  	 * @throws SAXException if there are parsing issues
97  	 * @throws IOException if there is an I/O issue
98  	 * @throws ConfigReadException if the given {@code fileContent} is not a valid Eclipse formatter template
99  	 * @throws ProfileNotFoundException if no profile could be found with the given {@code profileName} in the given {@code fileContent}
100 	 */
101 	@NonNull
102 	public static Map<String, String> getProfileSettings(String fileContent, String profileName) throws ConfigReadException, ProfileNotFoundException, IOException, SAXException {
103 		List<Node> profileNodes = getProfileNodes(fileContent);
104 		for (Node profileTag : profileNodes) {
105 			Node profileNameAttr = profileTag.getAttributes().getNamedItem(ATTRIBUTE_PROFILE_NAME);
106 			if (Objects.equals(profileName, profileNameAttr.getNodeValue())) {
107 				NodeList settingTagList = profileTag.getChildNodes();
108 				Map<String, String> config = new HashMap<>();
109 				for (int s = 0; s < settingTagList.getLength(); s++) {
110 					Node settingTag = settingTagList.item(s);
111 					if (TAG_NAME_SETTING.equals(settingTag.getNodeName())) {
112 						NamedNodeMap attributes = settingTag.getAttributes();
113 						Node keyAttr = attributes.getNamedItem(ATTRIBUTE_SETTING_ID);
114 						if (keyAttr != null) {
115 							Node valueAttr = attributes.getNamedItem(ATTRIBUTE_SETTING_VALUE);
116 							if (valueAttr != null) {
117 								config.put(keyAttr.getNodeValue(), valueAttr.getNodeValue());
118 							}
119 						}
120 					}
121 				}
122 
123 				return config;
124 			}
125 		}
126 
127 		throw new ProfileNotFoundException("Profile " + profileName + " not found in given file content");
128 	}
129 
130 	/**
131 	 * Parses the given {@code fileContent} and returns a {@link List} within all profile names found in that {@code fileContent}.
132 	 *
133 	 * @param fileContent the file content to parse
134 	 *
135 	 * @return a {@link List} within all profile names found in the given {@code fileContent}
136 	 *
137 	 * @throws SAXException if there are parsing issues
138 	 * @throws IOException if there is an I/O issue
139 	 * @throws ConfigReadException if the given {@code fileContent} is not a valid Eclipse formatter template
140 	 */
141 	@NonNull
142 	public static List<String> getProfileNames(String fileContent) throws ConfigReadException, IOException, SAXException {
143 		List<String> profileNames = new ArrayList<>();
144 
145 		List<Node> profileNodes = getProfileNodes(fileContent);
146 		for (Node profileTag : profileNodes) {
147 			Node profileNameAttr = profileTag.getAttributes().getNamedItem(ATTRIBUTE_PROFILE_NAME);
148 			if (profileNameAttr != null) {
149 				String profileName = profileNameAttr.getNodeValue();
150 				if (StringUtils.isNotBlank(profileName)) {
151 					profileNames.add(profileNameAttr.getNodeValue());
152 				}
153 			}
154 		}
155 
156 		return profileNames;
157 	}
158 
159 	/**
160 	 * Parses the given {@code fileContent} and returns a {@link List} within all profile {@link Node}s that were found in that {@code fileContent}.
161 	 *
162 	 * @param fileContent the file content to parse
163 	 *
164 	 * @return a {@link List} within all profile {@link Node}s that were found in the given {@code fileContent}
165 	 *
166 	 * @throws SAXException if there are parsing issues
167 	 * @throws IOException if there is an I/O issue
168 	 * @throws ConfigReadException if the given {@code fileContent} is not a valid Eclipse formatter template
169 	 */
170 	@NonNull
171 	private static List<Node> getProfileNodes(String fileContent) throws ConfigReadException, IOException, SAXException {
172 		if (fileContent == null) {
173 			throw new ConfigReadException("fileContent cannot be null");
174 		}
175 
176 		List<Node> profiles = new ArrayList<>();
177 
178 		Document formatterDoc;
179 		try (StringReader reader = new StringReader(fileContent)) {
180 			formatterDoc = XMLUtil.parse(new InputSource(reader), false, false, null, null);
181 		}
182 
183 		Element profilesTag = formatterDoc.getDocumentElement();
184 		if (profilesTag != null && TAG_NAME_PROFILES.equals(profilesTag.getNodeName())) {
185 			NodeList profileTagList = profilesTag.getElementsByTagName(TAG_NAME_PROFILE);
186 			if (profileTagList != null && profileTagList.getLength() > 0) {
187 				for (int p = 0; p < profileTagList.getLength(); p++) {
188 					Node profileTag = profileTagList.item(p);
189 					NamedNodeMap profileAttributes = profileTag.getAttributes();
190 					Node profileKindAttr = profileAttributes.getNamedItem(ATTRIBUTE_PROFILE_KIND);
191 					if (profileKindAttr != null && PROFILE_KIND.equals(profileKindAttr.getNodeValue())) {
192 						profiles.add(profileTag);
193 					}
194 				}
195 			} else {
196 				throw new ConfigReadException("No <profile> tag found in given file content");
197 			}
198 		} else {
199 			throw new ConfigReadException("No <profiles> tag found in config file");
200 		}
201 
202 		if (profiles.isEmpty()) {
203 			throw new ConfigReadException("No valid <profile> tag found in given file content");
204 		}
205 
206 		return profiles;
207 	}
208 }