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