package fr.adrienbrault.idea.symfony2plugin.action.ui; import com.intellij.openapi.project.Project; import com.jetbrains.php.lang.psi.elements.Method; import com.jetbrains.php.lang.psi.elements.PhpClass; import fr.adrienbrault.idea.symfony2plugin.dic.ContainerParameter; import fr.adrienbrault.idea.symfony2plugin.stubs.ContainerCollectionResolver; import fr.adrienbrault.idea.symfony2plugin.util.PhpElementsUtil; import fr.adrienbrault.idea.symfony2plugin.util.dict.ServiceTag; import fr.adrienbrault.idea.symfony2plugin.util.dict.ServiceUtil; import org.apache.commons.lang.StringUtils; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.xml.sax.SAXException; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import javax.xml.transform.OutputKeys; import javax.xml.transform.Transformer; import javax.xml.transform.TransformerException; import javax.xml.transform.TransformerFactory; import javax.xml.transform.dom.DOMSource; import javax.xml.transform.stream.StreamResult; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.StringWriter; import java.util.*; /** * @author Daniel Espendiller <daniel@espendiller.net> */ public class ServiceBuilder { public enum OutputType { Yaml, XML, } private List<MethodParameter.MethodModelParameter> methodModelParameter; private Project project; public ServiceBuilder(List<MethodParameter.MethodModelParameter> methodModelParameter, Project project) { this.methodModelParameter = methodModelParameter; this.project = project; } @Nullable public String build(OutputType outputType, String className, String serviceName) { HashMap<String, ArrayList<MethodParameter.MethodModelParameter>> methods = new HashMap<>(); for(MethodParameter.MethodModelParameter methodModelParameter: this.methodModelParameter) { String methodName = methodModelParameter.getName(); if(methodModelParameter.getMethod().getMethodType(false) == Method.MethodType.CONSTRUCTOR) { methodName = "__construct"; } if(methods.containsKey(methodModelParameter.getName())) { methods.get(methodName).add(methodModelParameter); } else { methods.put(methodName, new ArrayList<>(Collections.singletonList(methodModelParameter))); } } if(outputType == OutputType.Yaml) { return buildYaml(methods, className, serviceName); } if(outputType == OutputType.XML) { return buildXml(methods, className, serviceName); } return null; } @Nullable private List<String> getParameters(List<MethodParameter.MethodModelParameter> methodModelParameters) { boolean hasCall = false; ArrayList<String> methodCalls = new ArrayList<>(); // sort by indexes parameter methodModelParameters.sort((o1, o2) -> ((Integer) o1.getIndex()).compareTo(o2.getIndex())); for(MethodParameter.MethodModelParameter methodModelParameter: methodModelParameters) { // only add items which have at least one service parameter if(!hasCall && methodModelParameter.isPossibleService()) { hasCall = true; } // missing required parameter; add to service template, so use can correct it after String currentService = methodModelParameter.getCurrentService(); if(currentService == null || !methodModelParameter.isPossibleService()) { currentService = "?"; } methodCalls.add(currentService); } if(!hasCall || methodCalls.size() == 0) { return null; } return methodCalls; } @Nullable private String getClassAsParameter(String className) { if(className.startsWith("\\")) { className = className.substring(1); } for(Map.Entry<String, ContainerParameter> entry: ContainerCollectionResolver.getParameters(this.project).entrySet()) { String parameterValue = entry.getValue().getValue(); if(parameterValue != null) { if(parameterValue.startsWith("\\")) { parameterValue = parameterValue.substring(1); } if(parameterValue.equals(className)) { return entry.getKey(); } } } return null; } @Nullable private String buildXml(Map<String, ArrayList<MethodParameter.MethodModelParameter>> methods, String className, String serviceName) { DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance(); DocumentBuilder docBuilder = null; try { docBuilder = docFactory.newDocumentBuilder(); } catch (ParserConfigurationException e) { return null; } // root elements final Document doc = docBuilder.newDocument(); final Element rootElement = doc.createElement("service"); rootElement.setAttribute("id", serviceName); String classAsParameter = getClassAsParameter(className); rootElement.setAttribute("class", classAsParameter != null ? classAsParameter : className); doc.appendChild(rootElement); if(methods.containsKey("__construct")) { List<String> parameters = getParameters(methods.get("__construct")); if(parameters != null) { for(String parameter: parameters) { Element argument = doc.createElement("argument"); argument.setAttribute("id", parameter); argument.setAttribute("type", "service"); rootElement.appendChild(argument); } } methods.remove("__construct"); } for(Map.Entry<String, ArrayList<MethodParameter.MethodModelParameter>> entry: methods.entrySet()) { List<String> parameters = getParameters(entry.getValue()); if(parameters != null) { Element calls = doc.createElement("call"); calls.setAttribute("method", entry.getKey()); for(String parameter: parameters) { Element argument = doc.createElement("argument"); argument.setAttribute("id", parameter); argument.setAttribute("type", "service"); calls.appendChild(argument); } rootElement.appendChild(calls); } } serviceTagCallback(className, serviceTags -> { for (ServiceTag serviceTag : serviceTags) { try { // convert string to node Element node = DocumentBuilderFactory .newInstance() .newDocumentBuilder() .parse(new ByteArrayInputStream(serviceTag.toXmlString().getBytes())) .getDocumentElement(); rootElement.appendChild(doc.importNode(node, true)); } catch (SAXException | IOException | ParserConfigurationException ignored) { } } }); try { return getStringFromDocument(doc); } catch (TransformerException e) { return null; } } private static String getStringFromDocument(Document doc) throws TransformerException { StringWriter writer = new StringWriter(); StreamResult result = new StreamResult(writer); Transformer transformer = TransformerFactory.newInstance().newTransformer(); transformer.setOutputProperty(OutputKeys.INDENT, "yes"); transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes"); transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2"); transformer.transform(new DOMSource(doc), result); return writer.toString(); } private String buildYaml(Map<String, ArrayList<MethodParameter.MethodModelParameter>> methods, String className, String serviceName) { final String indent = "\t"; final List<String> lines = new ArrayList<>(); String classAsParameter = getClassAsParameter(className); lines.add(serviceName + ":"); lines.add(indent + "class: " + (classAsParameter != null ? "'%" + classAsParameter + "%'" : className)); if(methods.containsKey("__construct")) { List<String> parameters = getParameters(methods.get("__construct")); if(parameters != null) { lines.add(String.format("%sarguments: [%s]", indent, StringUtils.join(formatYamlService(parameters), ", "))); } methods.remove("__construct"); } List<String> calls = new ArrayList<>(); for(Map.Entry<String, ArrayList<MethodParameter.MethodModelParameter>> entry: methods.entrySet()) { List<String> parameters = getParameters(entry.getValue()); if(parameters != null) { calls.add(String.format("%s%s- [%s, [%s]]", indent, indent, entry.getKey(), StringUtils.join(formatYamlService(parameters), ", "))); } } if(calls.size() > 0) { lines.add(indent + "calls:"); lines.addAll(calls); } serviceTagCallback(className, serviceTags -> { lines.add(indent + "tags:"); for (ServiceTag serviceTag : serviceTags) { lines.add(indent + indent + serviceTag.toYamlString()); } }); return StringUtils.join(lines, "\n"); } private void serviceTagCallback(String className, TagCallbackInterface callback) { PhpClass phpClass = PhpElementsUtil.getClass(project, className); if(phpClass == null) { return; } List<ServiceTag> serviceTags = new ArrayList<>(); for (String tag : ServiceUtil.getPhpClassServiceTags(phpClass)) { ServiceTag serviceTag = new ServiceTag(phpClass, tag); ServiceUtil.decorateServiceTag(serviceTag); serviceTags.add(serviceTag); } if(serviceTags.size() == 0) { return; } callback.onTags(serviceTags); } private List<String> formatYamlService(List<String> parameters) { // append yaml syntax, more will follow... List<String> yamlSyntaxParameters = new ArrayList<>(); for(String parameter: parameters) { yamlSyntaxParameters.add(String.format("'@%s'", parameter)); } return yamlSyntaxParameters; } public interface TagCallbackInterface { void onTags(@NotNull List<ServiceTag> tags); } }