1 /************************************************************** 2 * 3 * Licensed to the Apache Software Foundation (ASF) under one 4 * or more contributor license agreements. See the NOTICE file 5 * distributed with this work for additional information 6 * regarding copyright ownership. The ASF licenses this file 7 * to you under the Apache License, Version 2.0 (the 8 * "License"); you may not use this file except in compliance 9 * with the License. You may obtain a copy of the License at 10 * 11 * http://www.apache.org/licenses/LICENSE-2.0 12 * 13 * Unless required by applicable law or agreed to in writing, 14 * software distributed under the License is distributed on an 15 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 16 * KIND, either express or implied. See the License for the 17 * specific language governing permissions and limitations 18 * under the License. 19 * 20 *************************************************************/ 21 22 package org.apache.openoffice.comp.sdbc.dbtools.sdbcx; 23 24 import java.util.ArrayList; 25 import java.util.Comparator; 26 import java.util.Iterator; 27 import java.util.List; 28 import java.util.TreeMap; 29 30 import org.apache.openoffice.comp.sdbc.dbtools.comphelper.CompHelper; 31 import org.apache.openoffice.comp.sdbc.dbtools.comphelper.OEnumerationByIndex; 32 import org.apache.openoffice.comp.sdbc.dbtools.util.PropertyIds; 33 import org.apache.openoffice.comp.sdbc.dbtools.util.Resources; 34 import org.apache.openoffice.comp.sdbc.dbtools.util.SharedResources; 35 import org.apache.openoffice.comp.sdbc.dbtools.util.StandardSQLState; 36 37 import com.sun.star.beans.UnknownPropertyException; 38 import com.sun.star.beans.XPropertySet; 39 import com.sun.star.container.ContainerEvent; 40 import com.sun.star.container.ElementExistException; 41 import com.sun.star.container.NoSuchElementException; 42 import com.sun.star.container.XContainer; 43 import com.sun.star.container.XContainerListener; 44 import com.sun.star.container.XEnumeration; 45 import com.sun.star.container.XEnumerationAccess; 46 import com.sun.star.container.XIndexAccess; 47 import com.sun.star.container.XNameAccess; 48 import com.sun.star.lang.EventObject; 49 import com.sun.star.lang.IllegalArgumentException; 50 import com.sun.star.lang.IndexOutOfBoundsException; 51 import com.sun.star.lang.WrappedTargetException; 52 import com.sun.star.lang.XServiceInfo; 53 import com.sun.star.lib.uno.helper.InterfaceContainer; 54 import com.sun.star.lib.uno.helper.WeakBase; 55 import com.sun.star.sdbc.SQLException; 56 import com.sun.star.sdbc.XColumnLocate; 57 import com.sun.star.sdbcx.XAppend; 58 import com.sun.star.sdbcx.XDataDescriptorFactory; 59 import com.sun.star.sdbcx.XDrop; 60 import com.sun.star.uno.AnyConverter; 61 import com.sun.star.uno.Type; 62 import com.sun.star.util.XRefreshListener; 63 import com.sun.star.util.XRefreshable; 64 65 /** 66 * Base class for a lazy-loaded collection of database objects. 67 */ 68 public abstract class OContainer extends WeakBase implements 69 XNameAccess, XIndexAccess, XEnumerationAccess, 70 XContainer, XColumnLocate, XRefreshable, XDataDescriptorFactory, 71 XAppend, XDrop, XServiceInfo { 72 73 private static final String[] services = new String[] { 74 "com.sun.star.sdbcx.Container" 75 }; 76 77 protected final Object lock; 78 private final boolean isCaseSensitive; 79 private TreeMap<String,XPropertySet> entriesByName; 80 private ArrayList<String> namesByIndex; 81 private InterfaceContainer containerListeners = new InterfaceContainer(); 82 private InterfaceContainer refreshListeners = new InterfaceContainer(); 83 84 private Comparator<String> caseSensitiveComparator = new Comparator<String>() { 85 @Override 86 public int compare(String x, String y) { 87 if (isCaseSensitive) { 88 return x.compareTo(y); 89 } else { 90 return x.compareToIgnoreCase(y); 91 } 92 } 93 }; 94 OContainer(Object lock, boolean isCaseSensitive)95 public OContainer(Object lock, boolean isCaseSensitive) { 96 this.lock = lock; 97 this.isCaseSensitive = isCaseSensitive; 98 this.entriesByName = new TreeMap<>(caseSensitiveComparator); 99 this.namesByIndex = new ArrayList<>(); 100 } 101 OContainer(Object lock, boolean isCaseSensitive, List<String> names)102 public OContainer(Object lock, boolean isCaseSensitive, List<String> names) throws ElementExistException { 103 this(lock, isCaseSensitive); 104 for (String name : names) { 105 if (entriesByName.containsKey(name)) { 106 throw new ElementExistException(name, this); 107 } 108 entriesByName.put(name, null); 109 namesByIndex.add(name); 110 } 111 } 112 refill(List<String> names)113 public void refill(List<String> names) { 114 // We only add new elements, as per the C++ implementation. 115 for (String name : names) { 116 if (!entriesByName.containsKey(name)) { 117 entriesByName.put(name, null); 118 namesByIndex.add(name); 119 } 120 } 121 } 122 123 // Would be from XComponent ;) 124 dispose()125 public void dispose() { 126 EventObject event = new EventObject(this); 127 containerListeners.disposeAndClear(event); 128 refreshListeners.disposeAndClear(event); 129 130 synchronized (lock) { 131 for (XPropertySet value : entriesByName.values()) { 132 CompHelper.disposeComponent(value); 133 } 134 entriesByName.clear(); 135 namesByIndex.clear(); 136 } 137 } 138 139 // XServiceInfo 140 getImplementationName()141 public String getImplementationName() { 142 return getClass().getName(); 143 } 144 145 @Override getSupportedServiceNames()146 public String[] getSupportedServiceNames() { 147 return services.clone(); 148 } 149 150 @Override supportsService(String serviceName)151 public boolean supportsService(String serviceName) { 152 for (String service : getSupportedServiceNames()) { 153 if (service.equals(serviceName)) { 154 return true; 155 } 156 } 157 return false; 158 } 159 160 // XIndexAccess 161 162 @Override getByIndex(int index)163 public Object getByIndex(int index) throws IndexOutOfBoundsException, WrappedTargetException { 164 synchronized (lock) { 165 if (index < 0 || index >= namesByIndex.size()) { 166 throw new IndexOutOfBoundsException(Integer.toString(index), this); 167 } 168 return getObject(index); 169 } 170 } 171 172 @Override getCount()173 public int getCount() { 174 synchronized (lock) { 175 return namesByIndex.size(); 176 } 177 } 178 179 // XNameAccess 180 181 @Override hasByName(String name)182 public boolean hasByName(String name) { 183 synchronized (lock) { 184 return entriesByName.containsKey(name); 185 } 186 } 187 188 @Override getByName(String name)189 public Object getByName(String name) throws NoSuchElementException, WrappedTargetException { 190 synchronized (lock) { 191 if (!entriesByName.containsKey(name)) { 192 String error = SharedResources.getInstance().getResourceStringWithSubstitution( 193 Resources.STR_NO_ELEMENT_NAME, "$name$", name); 194 throw new NoSuchElementException(error, this); 195 } 196 return getObject(indexOf(name)); 197 } 198 } 199 200 @Override getElementNames()201 public String[] getElementNames() { 202 synchronized (lock) { 203 String[] names = new String[namesByIndex.size()]; 204 return namesByIndex.toArray(names); 205 } 206 } 207 208 // XRefreshable 209 210 @Override refresh()211 public void refresh() { 212 Iterator<?> iterator; 213 synchronized (lock) { 214 for (XPropertySet value : entriesByName.values()) { 215 CompHelper.disposeComponent(value); 216 } 217 entriesByName.clear(); 218 namesByIndex.clear(); 219 220 impl_refresh(); 221 222 iterator = refreshListeners.iterator(); 223 } 224 if (iterator == null) { 225 // early disposal 226 return; 227 } 228 EventObject event = new EventObject(this); 229 while (iterator.hasNext()) { 230 XRefreshListener listener = (XRefreshListener) iterator.next(); 231 listener.refreshed(event); 232 } 233 } 234 235 @Override addRefreshListener(XRefreshListener listener)236 public void addRefreshListener(XRefreshListener listener) { 237 synchronized (lock) { 238 refreshListeners.add(listener); 239 } 240 } 241 242 @Override removeRefreshListener(XRefreshListener listener)243 public void removeRefreshListener(XRefreshListener listener) { 244 synchronized (lock) { 245 refreshListeners.remove(listener); 246 } 247 } 248 249 // XDataDescriptorFactory 250 251 @Override createDataDescriptor()252 public XPropertySet createDataDescriptor() { 253 synchronized (lock) { 254 return createDescriptor(); 255 } 256 } 257 258 // XAppend 259 260 @Override appendByDescriptor(XPropertySet descriptor)261 public void appendByDescriptor(XPropertySet descriptor) throws SQLException, ElementExistException { 262 Iterator<?> iterator; 263 ContainerEvent event; 264 synchronized (lock) { 265 String name = getNameForObject(descriptor); 266 267 if (entriesByName.containsKey(name)) { 268 throw new ElementExistException(name, this); 269 } 270 271 XPropertySet newlyCreated = appendObject(name, descriptor); 272 if (newlyCreated == null) { 273 throw new RuntimeException(); 274 } 275 276 name = getNameForObject(newlyCreated); 277 XPropertySet value = entriesByName.get(name); 278 if (value == null) { // this may happen when the derived class included it itself 279 entriesByName.put(name, newlyCreated); 280 namesByIndex.add(name); 281 } 282 283 // notify our container listeners 284 event = new ContainerEvent(this, name, newlyCreated, null); 285 iterator = containerListeners.iterator(); 286 } 287 while (iterator.hasNext()) { 288 XContainerListener listener = (XContainerListener) iterator.next(); 289 listener.elementInserted(event); 290 } 291 292 } 293 insertElement(String name, XPropertySet element)294 public void insertElement(String name, XPropertySet element) { 295 synchronized (lock) { 296 if (!entriesByName.containsKey(name)) { 297 entriesByName.put(name, element); 298 namesByIndex.add(name); 299 } 300 } 301 } 302 303 // XDrop 304 305 @Override dropByName(String name)306 public void dropByName(String name) throws SQLException, NoSuchElementException { 307 synchronized (lock) { 308 if (!entriesByName.containsKey(name)) { 309 throw new NoSuchElementException(name, this); 310 } 311 dropImpl(indexOf(name)); 312 } 313 } 314 315 @Override dropByIndex(int index)316 public void dropByIndex(int index) throws SQLException, IndexOutOfBoundsException { 317 synchronized (lock) { 318 if (index < 0 || index >= namesByIndex.size()) { 319 throw new IndexOutOfBoundsException(Integer.toString(index), this); 320 } 321 dropImpl(index); 322 } 323 } 324 325 dropImpl(int index)326 private void dropImpl(int index) throws SQLException { 327 dropImpl(index, true); 328 } 329 dropImpl(int index, boolean reallyDrop)330 private void dropImpl(int index, boolean reallyDrop) throws SQLException { 331 String name = namesByIndex.get(index); 332 if (reallyDrop) { 333 dropObject(index, name); 334 } 335 namesByIndex.remove(index); 336 XPropertySet propertySet = entriesByName.remove(name); 337 CompHelper.disposeComponent(propertySet); 338 339 ContainerEvent event = new ContainerEvent(this, name, null, null); 340 for (Iterator<?> iterator = containerListeners.iterator(); iterator.hasNext(); ) { 341 XContainerListener listener = (XContainerListener) iterator.next(); 342 listener.elementRemoved(event); 343 } 344 } 345 346 // XColumnLocate 347 348 @Override findColumn(String name)349 public int findColumn(String name) throws SQLException { 350 if (!entriesByName.containsKey(name)) { 351 String error = SharedResources.getInstance().getResourceStringWithSubstitution( 352 Resources.STR_UNKNOWN_COLUMN_NAME, "$columnname$", name); 353 throw new SQLException(error, this, StandardSQLState.SQL_COLUMN_NOT_FOUND.text(), 0, null); 354 } 355 return indexOf(name) + 1; // because columns start at one 356 } 357 358 359 // XEnumerationAccess 360 361 @Override createEnumeration()362 public XEnumeration createEnumeration() { 363 return new OEnumerationByIndex(this); 364 } 365 366 @Override addContainerListener(XContainerListener listener)367 public void addContainerListener(XContainerListener listener) { 368 containerListeners.add(listener); 369 } 370 371 @Override removeContainerListener(XContainerListener listener)372 public void removeContainerListener(XContainerListener listener) { 373 containerListeners.remove(listener); 374 } 375 376 @Override getElementType()377 public Type getElementType() { 378 return new Type(XPropertySet.class); 379 } 380 381 @Override hasElements()382 public boolean hasElements() { 383 synchronized (lock) { 384 return !entriesByName.isEmpty(); 385 } 386 } 387 indexOf(String name)388 protected int indexOf(String name) { 389 for (int i = 0; i < namesByIndex.size(); i++) { 390 if (namesByIndex.get(i).equals(name)) { 391 return i; 392 } 393 } 394 return -1; 395 } 396 397 /** return the object, if not existent it creates it. 398 * @param index 399 * The index of the object to create. 400 * @return ObjectType 401 */ getObject(int index)402 protected Object getObject(int index) throws WrappedTargetException { 403 String name = namesByIndex.get(index); 404 XPropertySet propertySet = entriesByName.get(name); 405 if (propertySet == null) { 406 try { 407 propertySet = createObject(name); 408 } catch (SQLException e) { 409 try { 410 dropImpl(index, false); 411 } catch (Exception ignored) { 412 } 413 throw new WrappedTargetException(e.getMessage(), this, e); 414 } 415 entriesByName.put(name, propertySet); 416 } 417 return propertySet; 418 } 419 420 /** clones the given descriptor 421 * 422 * The method calls createDescriptor to create a new, empty descriptor, and then copies all properties from 423 * descriptor to the new object, which is returned. 424 * 425 * This method might come handy in derived classes for implementing appendObject, when the object 426 * is not actually appended to any backend (e.g. for the columns collection of a descriptor object itself, 427 * where there is not yet a database backend to append the column to). 428 */ cloneDescriptor(XPropertySet descriptor)429 protected XPropertySet cloneDescriptor(XPropertySet descriptor) { 430 XPropertySet newDescriptor = createDescriptor(); 431 CompHelper.copyProperties(descriptor, newDescriptor); 432 return newDescriptor; 433 } 434 isCaseSensitive()435 protected boolean isCaseSensitive() { 436 return isCaseSensitive; 437 } 438 439 /** returns the name for the object. The default implementation ask for the property "Name". If this doesn't satisfy, it has to be overloaded. 440 * @param object The object where the name should be extracted. 441 * @return The name of the object. 442 */ getNameForObject(XPropertySet object)443 protected String getNameForObject(XPropertySet object) throws SQLException { 444 try { 445 Object name = object.getPropertyValue(PropertyIds.NAME.name); 446 return AnyConverter.toString(name); 447 } catch (WrappedTargetException | UnknownPropertyException | IllegalArgumentException exception) { 448 throw new SQLException("Error", this, StandardSQLState.SQL_GENERAL_ERROR.text(), 0, exception); 449 } 450 } 451 452 /// Will be called when a object was requested by one of the accessing methods like getByIndex. createObject(final String name)453 protected abstract XPropertySet createObject(final String name) throws SQLException; 454 455 /// Called when XDrop was called. dropObject(int index, String name)456 protected abstract void dropObject(int index, String name) throws SQLException; 457 458 // The implementing class should refresh their elements. impl_refresh()459 protected abstract void impl_refresh(); 460 461 /** Will be called when a new object should be generated by a call of createDataDescriptor; 462 * 463 * @return 464 * the returned object, empty, to be filled, and added to the collection. 465 */ createDescriptor()466 protected abstract XPropertySet createDescriptor(); 467 468 /** appends an object described by a descriptor, under a given name 469 @param _rForName 470 is the name under which the object should be appended. Guaranteed to not be empty. 471 This is passed for convenience only, since it's the result of a call of 472 getNameForObject for the given descriptor 473 @param descriptor 474 describes the object to append 475 @return 476 the new object which is to be inserted into the collection. This might be the result 477 of a call of <code>createObject( _rForName )</code>, or a clone of the descriptor. 478 */ appendObject(String _rForName, XPropertySet descriptor)479 protected abstract XPropertySet appendObject(String _rForName, XPropertySet descriptor) throws SQLException; 480 481 } 482