1 /*
2  * This file is part of the LibreOffice project.
3  *
4  * This Source Code Form is subject to the terms of the Mozilla Public
5  * License, v. 2.0. If a copy of the MPL was not distributed with this
6  * file, You can obtain one at http://mozilla.org/MPL/2.0/.
7  *
8  * This file incorporates work covered by the following license notice:
9  *
10  *   Licensed to the Apache Software Foundation (ASF) under one or more
11  *   contributor license agreements. See the NOTICE file distributed
12  *   with this work for additional information regarding copyright
13  *   ownership. The ASF licenses this file to you under the Apache
14  *   License, Version 2.0 (the "License"); you may not use this file
15  *   except in compliance with the License. You may obtain a copy of
16  *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
17  */
18 package org.libreoffice.report.pentaho;
19 
20 
21 import com.sun.star.lang.XServiceInfo;
22 import com.sun.star.lib.uno.helper.ComponentBase;
23 import com.sun.star.lib.uno.helper.PropertySetMixin;
24 import com.sun.star.sheet.FormulaLanguage;
25 import com.sun.star.sheet.FormulaMapGroup;
26 import com.sun.star.sheet.FormulaMapGroupSpecialOffset;
27 import com.sun.star.sheet.FormulaOpCodeMapEntry;
28 import com.sun.star.sheet.FormulaToken;
29 import com.sun.star.sheet.XFormulaOpCodeMapper;
30 import com.sun.star.uno.Any;
31 import com.sun.star.uno.Exception;
32 import com.sun.star.uno.Type;
33 import com.sun.star.uno.UnoRuntime;
34 import com.sun.star.uno.XComponentContext;
35 
36 import java.io.StringReader;
37 
38 import java.util.ArrayList;
39 import java.util.HashMap;
40 import java.util.Iterator;
41 import java.util.List;
42 import java.util.Map;
43 
44 import org.pentaho.reporting.libraries.base.config.Configuration;
45 import org.pentaho.reporting.libraries.formula.DefaultFormulaContext;
46 import org.pentaho.reporting.libraries.formula.function.FunctionRegistry;
47 import org.pentaho.reporting.libraries.formula.parser.FormulaParser;
48 import org.pentaho.reporting.libraries.formula.parser.GeneratedFormulaParserConstants;
49 import org.pentaho.reporting.libraries.formula.parser.GeneratedFormulaParserTokenManager;
50 import org.pentaho.reporting.libraries.formula.parser.JavaCharStream;
51 import org.pentaho.reporting.libraries.formula.parser.ParseException;
52 import org.pentaho.reporting.libraries.formula.parser.Token;
53 import org.pentaho.reporting.libraries.formula.parser.TokenMgrError;
54 
55 
56 public final class SOFormulaParser extends ComponentBase
57         implements com.sun.star.report.meta.XFormulaParser, XServiceInfo
58 {
59     /* Need this to get around generics array creation restriction */
60     private static class StringOpcodeMap extends HashMap<Integer,FormulaOpCodeMapEntry> {}
61 
62     public static final int SEPARATORS = 0;
63     public static final int ARRAY_SEPARATORS = 1;
64     public static final int UNARY_OPERATORS = 2;
65     public static final int BINARY_OPERATORS = 3;
66     public static final int FUNCTIONS = 4;
67     private final PropertySetMixin m_prophlp;
68     private static final String __serviceName = "com.sun.star.report.meta.FormulaParser";
69     private static final String OPERATORS = "org.pentaho.reporting.libraries.formula.operators.";
70     // attributes
71     final private List<FormulaOpCodeMapEntry> m_OpCodeMap = new ArrayList<FormulaOpCodeMapEntry>();
72     private XFormulaOpCodeMapper formulaOpCodeMapper = null;
73     private final Map<Integer,FormulaOpCodeMapEntry> parserAllOpCodes = new HashMap<Integer,FormulaOpCodeMapEntry>();
74     private final Map<String,FormulaOpCodeMapEntry> parserNames = new HashMap<String,FormulaOpCodeMapEntry>();
75     private final StringOpcodeMap[] groupOpCodes = new StringOpcodeMap[5];
76     private final List<FormulaOpCodeMapEntry> specialOpCodes = new ArrayList<FormulaOpCodeMapEntry>();
77     private int ownTokenCounter = 1000;
78     private final FormulaOpCodeMapEntry opCodePush;
79     private final FormulaParser parser;
80 
SOFormulaParser(final XComponentContext context)81     public SOFormulaParser(final XComponentContext context)
82     {
83 
84         final ClassLoader cl = java.lang.Thread.currentThread().getContextClassLoader();
85         Thread.currentThread().setContextClassLoader(this.getClass().getClassLoader());
86 
87         parser = new FormulaParser();
88         try
89         {
90             final XFormulaOpCodeMapper mapper = UnoRuntime.queryInterface(XFormulaOpCodeMapper.class, context.getServiceManager().createInstanceWithContext("simple.formula.FormulaOpCodeMapperObj", context));
91             FormulaOpCodeMapEntry[] opCodes = mapper.getAvailableMappings(FormulaLanguage.ODFF, FormulaMapGroup.FUNCTIONS);
92             final DefaultFormulaContext defaultContext = new DefaultFormulaContext();
93             final FunctionRegistry functionRegistry = defaultContext.getFunctionRegistry();
94 
95             String[] names = functionRegistry.getFunctionNames();
96             addOpCodes(names, opCodes, FUNCTIONS);
97             names = getOperators(defaultContext, OPERATORS);
98             opCodes = mapper.getAvailableMappings(FormulaLanguage.ODFF, FormulaMapGroup.UNARY_OPERATORS);
99             addOpCodes(names, opCodes, UNARY_OPERATORS);
100             opCodes = mapper.getAvailableMappings(FormulaLanguage.ODFF, FormulaMapGroup.BINARY_OPERATORS);
101             addOpCodes(names, opCodes, BINARY_OPERATORS);
102 
103             names = GeneratedFormulaParserConstants.tokenImage.clone();
104             for (int i = 0; i < names.length; i++)
105             {
106                 final String token = names[i];
107                 if (token != null && token.length() > 0 && token.charAt(0) == '"')
108                 {
109                     names[i] = token.substring(1, token.length() - 1);
110                 }
111             }
112             opCodes = mapper.getAvailableMappings(FormulaLanguage.ODFF, FormulaMapGroup.SEPARATORS);
113             addOpCodes(names, opCodes, SEPARATORS, false);
114 
115             opCodes = mapper.getAvailableMappings(FormulaLanguage.ODFF, FormulaMapGroup.ARRAY_SEPARATORS);
116             addOpCodes(names, opCodes, ARRAY_SEPARATORS, false);
117 
118             opCodes = mapper.getAvailableMappings(FormulaLanguage.ODFF, FormulaMapGroup.SPECIAL);
119 
120             for (int i = 0; i < opCodes.length; i++)
121             {
122                 final FormulaOpCodeMapEntry opCode = opCodes[i];
123                 parserAllOpCodes.put(opCode.Token.OpCode, opCode);
124                 specialOpCodes.add(opCode);
125             }
126         }
127         catch (Exception ex)
128         {
129             ex.printStackTrace();
130         }
131         opCodePush = specialOpCodes.get(FormulaMapGroupSpecialOffset.PUSH);
132         Thread.currentThread().setContextClassLoader(cl);
133         // use the last parameter of the PropertySetMixin constructor
134         // for your optional attributes if necessary. See the documentation
135         // of the PropertySetMixin helper for further information.
136         // Ensure that your attributes are initialized correctly!
137         m_prophlp = new PropertySetMixin(context, this,
138                 new Type(com.sun.star.report.meta.XFormulaParser.class), null);
139     }
140 
141     // com.sun.star.sheet.XFormulaParser:
parseFormula(String aFormula, com.sun.star.table.CellAddress aReferencePos)142     public com.sun.star.sheet.FormulaToken[] parseFormula(String aFormula, com.sun.star.table.CellAddress aReferencePos)
143     {
144         final ArrayList<FormulaToken> tokens = new ArrayList<FormulaToken>();
145         if (!"=".equals(aFormula))
146         {
147             String formula;
148             if (aFormula.charAt(0) == '=')
149             {
150                 formula = aFormula.substring(1);
151             }
152             else
153             {
154                 formula = aFormula;
155             }
156             final ArrayList<String> images = new ArrayList<String>();
157             try
158             {
159                 int brackets = 0;
160                 final GeneratedFormulaParserTokenManager tokenParser = new GeneratedFormulaParserTokenManager(new JavaCharStream(new StringReader(formula), 1, 1));
161                 Token token = tokenParser.getNextToken();
162                 while (token.kind != GeneratedFormulaParserConstants.EOF)
163                 {
164                     final FormulaToken formulaToken;
165                     images.add(token.image);
166                     final String upper = token.image.toUpperCase();
167                     if (parserNames.containsKey(upper))
168                     {
169                         if ("(".equals(token.image))
170                         {
171                             brackets++;
172                         }
173                         else if (")".equals(token.image))
174                         {
175                             --brackets;
176                         }
177                         final FormulaOpCodeMapEntry opCode = parserNames.get(upper);
178                         formulaToken = opCode.Token;
179                     }
180                     else if (token.kind == GeneratedFormulaParserConstants.WHITESPACE)
181                     {
182                         final FormulaOpCodeMapEntry opCode = specialOpCodes.get(FormulaMapGroupSpecialOffset.SPACES);
183                         formulaToken = opCode.Token;
184                     }
185                     else
186                     {
187                         formulaToken = new FormulaToken();
188                         formulaToken.OpCode = opCodePush.Token.OpCode;
189                         formulaToken.Data = new Any(Type.STRING, token.image);
190                     }
191 
192                     tokens.add(formulaToken);
193                     token = tokenParser.getNextToken();
194                 }
195                 if (brackets > 0)
196                 {
197                     final FormulaOpCodeMapEntry opCode = parserNames.get(")");
198                     while (brackets-- != 0)
199                     {
200                         formula = formula.concat(")");
201                         images.add(")");
202                         tokens.add(opCode.Token);
203                     }
204 
205                 }
206 
207                 parser.parse(formula);
208             }
209             catch (ParseException ex)
210             {
211                 boolean found = false;
212                 // error occurred so all token must be bad
213                 for (int i = 0; i < tokens.size(); i++)
214                 {
215                     if (!found && ex.currentToken != null && images.get(i).equals(ex.currentToken.image))
216                     {
217                         found = true;
218                     }
219                     if (found)
220                     {
221                         final FormulaToken dest = new FormulaToken();
222                         dest.OpCode = specialOpCodes.get(FormulaMapGroupSpecialOffset.BAD).Token.OpCode;
223                         dest.Data = new Any(Type.STRING, images.get(i));
224                         tokens.remove(i);
225                         tokens.add(i, dest);
226                     }
227                 }
228             }
229             catch (java.lang.Exception e)
230             {
231             }
232             catch (TokenMgrError e)
233             {
234             }
235         }
236         return tokens.toArray(new FormulaToken[tokens.size()]);
237     }
238 
printFormula(com.sun.star.sheet.FormulaToken[] aTokens, com.sun.star.table.CellAddress aReferencePos)239     public String printFormula(com.sun.star.sheet.FormulaToken[] aTokens, com.sun.star.table.CellAddress aReferencePos)
240     {
241         final StringBuffer ret = new StringBuffer();
242         for (int i = 0; i < aTokens.length; i++)
243         {
244             final FormulaToken formulaToken = aTokens[i];
245             if (formulaToken.OpCode == opCodePush.Token.OpCode && !formulaToken.Data.equals(Any.VOID))
246             {
247                 ret.append(formulaToken.Data);
248             }
249             else if (parserAllOpCodes.containsKey(formulaToken.OpCode))
250             {
251                 final FormulaOpCodeMapEntry opCode = parserAllOpCodes.get(formulaToken.OpCode);
252                 if (opCode.Name.length() > 0)
253                 {
254                     ret.append(opCode.Name);
255                 }
256                 else if (!formulaToken.Data.equals(Any.VOID))
257                 {
258                     ret.append(formulaToken.Data);
259                 }
260             }
261         }
262         return ret.toString();
263     }
264 
265     // com.sun.star.beans.XPropertySet:
getPropertySetInfo()266     public com.sun.star.beans.XPropertySetInfo getPropertySetInfo()
267     {
268         return m_prophlp.getPropertySetInfo();
269     }
270 
setPropertyValue(String aPropertyName, Object aValue)271     public void setPropertyValue(String aPropertyName, Object aValue) throws com.sun.star.beans.UnknownPropertyException, com.sun.star.beans.PropertyVetoException, com.sun.star.lang.IllegalArgumentException, com.sun.star.lang.WrappedTargetException
272     {
273         m_prophlp.setPropertyValue(aPropertyName, aValue);
274     }
275 
getPropertyValue(String aPropertyName)276     public Object getPropertyValue(String aPropertyName) throws com.sun.star.beans.UnknownPropertyException, com.sun.star.lang.WrappedTargetException
277     {
278         return m_prophlp.getPropertyValue(aPropertyName);
279     }
280 
addPropertyChangeListener(String aPropertyName, com.sun.star.beans.XPropertyChangeListener xListener)281     public void addPropertyChangeListener(String aPropertyName, com.sun.star.beans.XPropertyChangeListener xListener) throws com.sun.star.beans.UnknownPropertyException, com.sun.star.lang.WrappedTargetException
282     {
283         m_prophlp.addPropertyChangeListener(aPropertyName, xListener);
284     }
285 
removePropertyChangeListener(String aPropertyName, com.sun.star.beans.XPropertyChangeListener xListener)286     public void removePropertyChangeListener(String aPropertyName, com.sun.star.beans.XPropertyChangeListener xListener) throws com.sun.star.beans.UnknownPropertyException, com.sun.star.lang.WrappedTargetException
287     {
288         m_prophlp.removePropertyChangeListener(aPropertyName, xListener);
289     }
290 
addVetoableChangeListener(String aPropertyName, com.sun.star.beans.XVetoableChangeListener xListener)291     public void addVetoableChangeListener(String aPropertyName, com.sun.star.beans.XVetoableChangeListener xListener) throws com.sun.star.beans.UnknownPropertyException, com.sun.star.lang.WrappedTargetException
292     {
293         m_prophlp.addVetoableChangeListener(aPropertyName, xListener);
294     }
295 
removeVetoableChangeListener(String aPropertyName, com.sun.star.beans.XVetoableChangeListener xListener)296     public void removeVetoableChangeListener(String aPropertyName, com.sun.star.beans.XVetoableChangeListener xListener) throws com.sun.star.beans.UnknownPropertyException, com.sun.star.lang.WrappedTargetException
297     {
298         m_prophlp.removeVetoableChangeListener(aPropertyName, xListener);
299     }
300 
301     // com.sun.star.report.meta.XFormulaParser:
getOpCodeMap()302     public com.sun.star.sheet.FormulaOpCodeMapEntry[] getOpCodeMap()
303     {
304         return m_OpCodeMap.toArray(new FormulaOpCodeMapEntry[m_OpCodeMap.size()]);
305     }
306 
setOpCodeMap(com.sun.star.sheet.FormulaOpCodeMapEntry[] the_value)307     public void setOpCodeMap(com.sun.star.sheet.FormulaOpCodeMapEntry[] the_value)
308     {
309     }
310 
getImplementationName()311     public String getImplementationName()
312     {
313         return SOFormulaParser.class.getName();
314     }
315 
supportsService(String sServiceName)316     public boolean supportsService(String sServiceName)
317     {
318         return sServiceName.equals(__serviceName);
319     }
320 
getSupportedServiceNames()321     public String[] getSupportedServiceNames()
322     {
323         return getServiceNames();
324     }
325 
326     /**
327      * This method is a simple helper function to used in the static component initialisation functions as well as
328      * in getSupportedServiceNames.
329      */
getServiceNames()330     public static String[] getServiceNames()
331     {
332         return new String[]
333                 {
334                     __serviceName
335                 };
336     }
337 
getFormulaOpCodeMapper()338     public XFormulaOpCodeMapper getFormulaOpCodeMapper()
339     {
340         if (formulaOpCodeMapper == null)
341         {
342             formulaOpCodeMapper = new SOFormulaOpCodeMapper(this);
343         }
344 
345         return formulaOpCodeMapper;
346     }
347 
addOpCodes(String[] names, FormulaOpCodeMapEntry[] opCodes, int group)348     private void addOpCodes(String[] names, FormulaOpCodeMapEntry[] opCodes, int group)
349     {
350         addOpCodes(names, opCodes, group, true);
351     }
352 
addOpCodes(String[] names, FormulaOpCodeMapEntry[] opCodes, int group, boolean add)353     private void addOpCodes(String[] names, FormulaOpCodeMapEntry[] opCodes, int group, boolean add)
354     {
355         groupOpCodes[group] = new StringOpcodeMap();
356         for (int j = 0; j < names.length; j++)
357         {
358             FormulaOpCodeMapEntry opCode = null;
359             int i = 0;
360             for (; i < opCodes.length; i++)
361             {
362                 opCode = opCodes[i];
363                 if (names[j].equals(opCode.Name))
364                 {
365                     break;
366                 }
367             }
368             if (i >= opCodes.length)
369             {
370                 if (!add)
371                 {
372                     continue;
373                 }
374                 final FormulaToken token = new FormulaToken(ownTokenCounter++, Any.VOID);
375                 opCode = new FormulaOpCodeMapEntry(names[j], token);
376             }
377             parserNames.put(names[j], opCode);
378             parserAllOpCodes.put(opCode.Token.OpCode, opCode);
379             groupOpCodes[group].put(opCode.Token.OpCode, opCode);
380         }
381     }
382 
getNames()383     public Map<String,FormulaOpCodeMapEntry> getNames()
384     {
385         return parserNames;
386     }
387 
getGroup(int group)388     public Map<Integer,FormulaOpCodeMapEntry> getGroup(int group)
389     {
390         return groupOpCodes[group];
391     }
392 
getOperators(DefaultFormulaContext defaultContext, final String _kind)393     private String[] getOperators(DefaultFormulaContext defaultContext, final String _kind)
394     {
395         final ArrayList<String> ops = new ArrayList<String>();
396         final Configuration configuration = defaultContext.getConfiguration();
397         final Iterator iter = configuration.findPropertyKeys(_kind);
398         while (iter.hasNext())
399         {
400             final String configKey = (String) iter.next();
401             if (!configKey.endsWith(".class"))
402             {
403                 continue;
404             }
405             final String operatorClass = configuration.getConfigProperty(configKey);
406             if (operatorClass == null)
407             {
408                 continue;
409             }
410             if (operatorClass.length() == 0)
411             {
412                 continue;
413             }
414             final String tokenKey = configKey.substring(0, configKey.length() - ".class".length()) + ".token";
415             final String token = configuration.getConfigProperty(tokenKey);
416             if (token == null)
417             {
418                 continue;
419             }
420             ops.add(token.trim());
421         }
422         return ops.toArray(new String[ops.size()]);
423     }
424 
getSpecialOpCodes()425     public List<FormulaOpCodeMapEntry> getSpecialOpCodes()
426     {
427         return specialOpCodes;
428     }
429 }
430 
431