1 /****************************************************************************
2 **
3 ** Copyright (C) 2015 The Qt Company Ltd.
4 ** Contact: http://www.qt.io/licensing/
5 **
6 ** This file is part of the QtScript module of the Qt Toolkit.
7 **
8 ** $QT_BEGIN_LICENSE:LGPL-ONLY$
9 ** GNU Lesser General Public License Usage
10 ** This file may be used under the terms of the GNU Lesser
11 ** General Public License version 2.1 as published by the Free Software
12 ** Foundation and appearing in the file LICENSE.LGPL included in the
13 ** packaging of this file.  Please review the following information to
14 ** ensure the GNU Lesser General Public License version 2.1 requirements
15 ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
16 **
17 ** If you have questions regarding the use of this file, please contact
18 ** us via http://www.qt.io/contact-us/.
19 **
20 ** $QT_END_LICENSE$
21 **
22 ****************************************************************************/
23 
24 #include "config.h"
25 #include "qscriptvalueiterator.h"
26 
27 #include "qscriptstring.h"
28 #include "qscriptengine.h"
29 #include "qscriptengine_p.h"
30 #include "qscriptvalue_p.h"
31 #include "qlinkedlist.h"
32 
33 
34 #include "JSObject.h"
35 #include "PropertyNameArray.h"
36 #include "JSArray.h"
37 #include "JSFunction.h"
38 
39 QT_BEGIN_NAMESPACE
40 
41 /*!
42   \since 4.3
43   \class QScriptValueIterator
44 
45   \brief The QScriptValueIterator class provides a Java-style iterator for QScriptValue.
46 
47   \ingroup script
48 
49 
50   The QScriptValueIterator constructor takes a QScriptValue as
51   argument.  After construction, the iterator is located at the very
52   beginning of the sequence of properties. Here's how to iterate over
53   all the properties of a QScriptValue:
54 
55   \snippet doc/src/snippets/code/src_script_qscriptvalueiterator.cpp 0
56 
57   The next() advances the iterator. The name(), value() and flags()
58   functions return the name, value and flags of the last item that was
59   jumped over.
60 
61   If you want to remove properties as you iterate over the
62   QScriptValue, use remove(). If you want to modify the value of a
63   property, use setValue().
64 
65   Note that QScriptValueIterator only iterates over the QScriptValue's
66   own properties; i.e. it does not follow the prototype chain. You can
67   use a loop like this to follow the prototype chain:
68 
69   \snippet doc/src/snippets/code/src_script_qscriptvalueiterator.cpp 1
70 
71   Note that QScriptValueIterator will not automatically skip over
72   properties that have the QScriptValue::SkipInEnumeration flag set;
73   that flag only affects iteration in script code.  If you want, you
74   can skip over such properties with code like the following:
75 
76   \snippet doc/src/snippets/code/src_script_qscriptvalueiterator.cpp 2
77 
78   \sa QScriptValue::property()
79 */
80 
81 class QScriptValueIteratorPrivate
82 {
83 public:
QScriptValueIteratorPrivate()84     QScriptValueIteratorPrivate()
85         : initialized(false)
86     {}
87 
~QScriptValueIteratorPrivate()88     ~QScriptValueIteratorPrivate()
89     {
90         if (!initialized)
91             return;
92         QScriptEnginePrivate *eng_p = engine();
93         if (!eng_p)
94             return;
95         QScript::APIShim shim(eng_p);
96         propertyNames.clear(); //destroying the identifiers need to be done under the APIShim guard
97     }
98 
object() const99     QScriptValuePrivate *object() const
100     {
101         return QScriptValuePrivate::get(objectValue);
102     }
103 
engine() const104     QScriptEnginePrivate *engine() const
105     {
106         return QScriptEnginePrivate::get(objectValue.engine());
107     }
108 
ensureInitialized()109     void ensureInitialized()
110     {
111         if (initialized)
112             return;
113         QScriptEnginePrivate *eng_p = engine();
114         QScript::APIShim shim(eng_p);
115         JSC::ExecState *exec = eng_p->globalExec();
116         JSC::PropertyNameArray propertyNamesArray(exec);
117         JSC::asObject(object()->jscValue)->getOwnPropertyNames(exec, propertyNamesArray, JSC::IncludeDontEnumProperties);
118 
119         JSC::PropertyNameArray::const_iterator propertyNamesIt = propertyNamesArray.begin();
120         for(; propertyNamesIt != propertyNamesArray.end(); ++propertyNamesIt) {
121             propertyNames.append(*propertyNamesIt);
122         }
123         it = propertyNames.begin();
124         initialized = true;
125     }
126 
127     QScriptValue objectValue;
128     QLinkedList<JSC::Identifier> propertyNames;
129     QLinkedList<JSC::Identifier>::iterator it;
130     QLinkedList<JSC::Identifier>::iterator current;
131     bool initialized;
132 };
133 
134 /*!
135   Constructs an iterator for traversing \a object. The iterator is
136   set to be at the front of the sequence of properties (before the
137   first property).
138 */
QScriptValueIterator(const QScriptValue & object)139 QScriptValueIterator::QScriptValueIterator(const QScriptValue &object)
140     : d_ptr(0)
141 {
142     if (object.isObject()) {
143         d_ptr.reset(new QScriptValueIteratorPrivate());
144         d_ptr->objectValue = object;
145     }
146 }
147 
148 /*!
149   Destroys the iterator.
150 */
~QScriptValueIterator()151 QScriptValueIterator::~QScriptValueIterator()
152 {
153 }
154 
155 /*!
156   Returns true if there is at least one item ahead of the iterator
157   (i.e. the iterator is \e not at the back of the property sequence);
158   otherwise returns false.
159 
160   \sa next(), hasPrevious()
161 */
hasNext() const162 bool QScriptValueIterator::hasNext() const
163 {
164     Q_D(const QScriptValueIterator);
165     if (!d || !d->engine())
166         return false;
167 
168     const_cast<QScriptValueIteratorPrivate*>(d)->ensureInitialized();
169     return d->it != d->propertyNames.end();
170 }
171 
172 /*!
173   Advances the iterator by one position.
174 
175   Calling this function on an iterator located at the back of the
176   container leads to undefined results.
177 
178   \sa hasNext(), previous(), name()
179 */
next()180 void QScriptValueIterator::next()
181 {
182     Q_D(QScriptValueIterator);
183     if (!d)
184         return;
185     d->ensureInitialized();
186 
187     d->current = d->it;
188     ++(d->it);
189 }
190 
191 /*!
192   Returns true if there is at least one item behind the iterator
193   (i.e. the iterator is \e not at the front of the property sequence);
194   otherwise returns false.
195 
196   \sa previous(), hasNext()
197 */
hasPrevious() const198 bool QScriptValueIterator::hasPrevious() const
199 {
200     Q_D(const QScriptValueIterator);
201     if (!d || !d->engine())
202         return false;
203 
204     const_cast<QScriptValueIteratorPrivate*>(d)->ensureInitialized();
205     return d->it != d->propertyNames.begin();
206 }
207 
208 /*!
209   Moves the iterator back by one position.
210 
211   Calling this function on an iterator located at the front of the
212   container leads to undefined results.
213 
214   \sa hasPrevious(), next(), name()
215 */
previous()216 void QScriptValueIterator::previous()
217 {
218     Q_D(QScriptValueIterator);
219     if (!d)
220         return;
221     d->ensureInitialized();
222     --(d->it);
223     d->current = d->it;
224 }
225 
226 /*!
227   Moves the iterator to the front of the QScriptValue (before the
228   first property).
229 
230   \sa toBack(), next()
231 */
toFront()232 void QScriptValueIterator::toFront()
233 {
234     Q_D(QScriptValueIterator);
235     if (!d)
236         return;
237     d->ensureInitialized();
238     d->it = d->propertyNames.begin();
239 }
240 
241 /*!
242   Moves the iterator to the back of the QScriptValue (after the
243   last property).
244 
245   \sa toFront(), previous()
246 */
toBack()247 void QScriptValueIterator::toBack()
248 {
249     Q_D(QScriptValueIterator);
250     if (!d)
251         return;
252     d->ensureInitialized();
253     d->it = d->propertyNames.end();
254 }
255 
256 /*!
257   Returns the name of the last property that was jumped over using
258   next() or previous().
259 
260   \sa value(), flags()
261 */
name() const262 QString QScriptValueIterator::name() const
263 {
264     Q_D(const QScriptValueIterator);
265     if (!d || !d->initialized || !d->engine())
266         return QString();
267     return d->current->ustring();
268 }
269 
270 /*!
271   \since 4.4
272 
273   Returns the name of the last property that was jumped over using
274   next() or previous().
275 */
scriptName() const276 QScriptString QScriptValueIterator::scriptName() const
277 {
278     Q_D(const QScriptValueIterator);
279     if (!d || !d->initialized || !d->engine())
280         return QScriptString();
281     return d->engine()->toStringHandle(*d->current);
282 }
283 
284 /*!
285   Returns the value of the last property that was jumped over using
286   next() or previous().
287 
288   \sa setValue(), name()
289 */
value() const290 QScriptValue QScriptValueIterator::value() const
291 {
292     Q_D(const QScriptValueIterator);
293     if (!d || !d->initialized || !d->engine())
294         return QScriptValue();
295     QScript::APIShim shim(d->engine());
296     JSC::JSValue jsValue = d->object()->property(*d->current);
297     return d->engine()->scriptValueFromJSCValue(jsValue);
298 }
299 
300 /*!
301   Sets the \a value of the last property that was jumped over using
302   next() or previous().
303 
304   \sa value(), name()
305 */
setValue(const QScriptValue & value)306 void QScriptValueIterator::setValue(const QScriptValue &value)
307 {
308     Q_D(QScriptValueIterator);
309     if (!d || !d->initialized || !d->engine())
310         return;
311     QScript::APIShim shim(d->engine());
312     JSC::JSValue jsValue = d->engine()->scriptValueToJSCValue(value);
313     d->object()->setProperty(*d->current, jsValue);
314 }
315 
316 /*!
317   Returns the flags of the last property that was jumped over using
318   next() or previous().
319 
320   \sa value()
321 */
flags() const322 QScriptValue::PropertyFlags QScriptValueIterator::flags() const
323 {
324     Q_D(const QScriptValueIterator);
325     if (!d || !d->initialized || !d->engine())
326         return 0;
327     QScript::APIShim shim(d->engine());
328     return d->object()->propertyFlags(*d->current);
329 }
330 
331 /*!
332   Removes the last property that was jumped over using next()
333   or previous().
334 
335   \sa setValue()
336 */
remove()337 void QScriptValueIterator::remove()
338 {
339     Q_D(QScriptValueIterator);
340     if (!d || !d->initialized || !d->engine())
341         return;
342     QScript::APIShim shim(d->engine());
343     d->object()->setProperty(*d->current, JSC::JSValue());
344     d->propertyNames.erase(d->current);
345 }
346 
347 /*!
348   Makes the iterator operate on \a object. The iterator is set to be
349   at the front of the sequence of properties (before the first
350   property).
351 */
operator =(QScriptValue & object)352 QScriptValueIterator& QScriptValueIterator::operator=(QScriptValue &object)
353 {
354     d_ptr.reset();
355     if (object.isObject()) {
356         d_ptr.reset(new QScriptValueIteratorPrivate());
357         d_ptr->objectValue = object;
358     }
359     return *this;
360 }
361 
362 QT_END_NAMESPACE
363