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