56716d6e7513fd41cf23b78ec30c2df038c8a132
[Sone.git] / src / main / java / net / pterodactylus / sone / core / WebOfTrustUpdater.java
1 /*
2  * Sone - WebOfTrustUpdater.java - Copyright © 2012 David Roden
3  *
4  * This program is free software: you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation, either version 3 of the License, or
7  * (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
16  */
17
18 package net.pterodactylus.sone.core;
19
20 import java.util.concurrent.BlockingQueue;
21 import java.util.concurrent.LinkedBlockingQueue;
22 import java.util.logging.Level;
23 import java.util.logging.Logger;
24
25 import net.pterodactylus.sone.freenet.plugin.PluginException;
26 import net.pterodactylus.sone.freenet.wot.DefaultIdentity;
27 import net.pterodactylus.sone.freenet.wot.Identity;
28 import net.pterodactylus.sone.freenet.wot.OwnIdentity;
29 import net.pterodactylus.sone.freenet.wot.Trust;
30 import net.pterodactylus.sone.freenet.wot.WebOfTrustConnector;
31 import net.pterodactylus.sone.freenet.wot.WebOfTrustException;
32 import net.pterodactylus.util.logging.Logging;
33 import net.pterodactylus.util.service.AbstractService;
34 import net.pterodactylus.util.validation.Validation;
35
36 /**
37  * Updates WebOfTrust identity data in a background thread because communicating
38  * with the WebOfTrust plugin can potentially last quite long.
39  *
40  * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
41  */
42 public class WebOfTrustUpdater extends AbstractService {
43
44         /** The logger. */
45         private static final Logger logger = Logging.getLogger(WebOfTrustUpdater.class);
46
47         /** Stop job. */
48         @SuppressWarnings("synthetic-access")
49         private final WebOfTrustUpdateJob stopJob = new WebOfTrustUpdateJob();
50
51         /** The web of trust connector. */
52         private final WebOfTrustConnector webOfTrustConnector;
53
54         /** The queue for jobs. */
55         private final BlockingQueue<WebOfTrustUpdateJob> updateJobs = new LinkedBlockingQueue<WebOfTrustUpdateJob>();
56
57         /**
58          * Creates a new trust updater.
59          *
60          * @param webOfTrustConnector
61          *            The web of trust connector
62          */
63         public WebOfTrustUpdater(WebOfTrustConnector webOfTrustConnector) {
64                 super("Trust Updater");
65                 this.webOfTrustConnector = webOfTrustConnector;
66         }
67
68         //
69         // ACTIONS
70         //
71
72         /**
73          * Retrieves the trust relation between the truster and the trustee. This
74          * method will return immediately and perform a trust update in the
75          * background.
76          *
77          * @param truster
78          *            The identity giving the trust
79          * @param trustee
80          *            The identity receiving the trust
81          */
82         public void getTrust(OwnIdentity truster, Identity trustee) {
83                 GetTrustJob getTrustJob = new GetTrustJob(truster, trustee);
84                 if (!updateJobs.contains(getTrustJob)) {
85                         logger.log(Level.FINER, "Adding Trust Update Job: " + getTrustJob);
86                         try {
87                                 updateJobs.put(getTrustJob);
88                         } catch (InterruptedException ie1) {
89                                 /* the queue is unbounded so it should never block. */
90                         }
91                 }
92         }
93
94         /**
95          * Updates the trust relation between the truster and the trustee. This
96          * method will return immediately and perform a trust update in the
97          * background.
98          *
99          * @param truster
100          *            The identity giving the trust
101          * @param trustee
102          *            The identity receiving the trust
103          * @param score
104          *            The new level of trust (from -100 to 100, may be {@code null}
105          *            to remove the trust completely)
106          * @param comment
107          *            The comment of the trust relation
108          */
109         public void setTrust(OwnIdentity truster, Identity trustee, Integer score, String comment) {
110                 SetTrustJob setTrustJob = new SetTrustJob(truster, trustee, score, comment);
111                 if (updateJobs.contains(setTrustJob)) {
112                         updateJobs.remove(setTrustJob);
113                 }
114                 logger.log(Level.FINER, "Adding Trust Update Job: " + setTrustJob);
115                 try {
116                         updateJobs.put(setTrustJob);
117                 } catch (InterruptedException e) {
118                         /* the queue is unbounded so it should never block. */
119                 }
120         }
121
122         /**
123          * Adds the given context to the given own identity.
124          *
125          * @param ownIdentity
126          *            The own identity to add the context to
127          * @param context
128          *            The context to add
129          */
130         public void addContext(OwnIdentity ownIdentity, String context) {
131                 addContextWait(ownIdentity, context, false);
132         }
133
134         /**
135          * Adds the given context to the given own identity, waiting for completion
136          * of the operation.
137          *
138          * @param ownIdentity
139          *            The own identity to add the context to
140          * @param context
141          *            The context to add
142          * @return {@code true} if the context was added successfully, {@code false}
143          *         otherwise
144          */
145         public boolean addContextWait(OwnIdentity ownIdentity, String context) {
146                 return addContextWait(ownIdentity, context, true);
147         }
148
149         /**
150          * Adds the given context to the given own identity, waiting for completion
151          * of the operation.
152          *
153          * @param ownIdentity
154          *            The own identity to add the context to
155          * @param context
156          *            The context to add
157          * @param wait
158          *            {@code true} to wait for the end of the operation,
159          *            {@code false} to return immediately
160          * @return {@code true} if the context was added successfully, {@code false}
161          *         if the context was not added successfully, or if the job should
162          *         not wait for completion
163          */
164         private boolean addContextWait(OwnIdentity ownIdentity, String context, boolean wait) {
165                 AddContextJob addContextJob = new AddContextJob(ownIdentity, context);
166                 if (!updateJobs.contains(addContextJob)) {
167                         logger.log(Level.FINER, "Adding Context Job: " + addContextJob);
168                         try {
169                                 updateJobs.put(addContextJob);
170                         } catch (InterruptedException ie1) {
171                                 /* the queue is unbounded so it should never block. */
172                         }
173                         if (wait) {
174                                 return addContextJob.waitForCompletion();
175                         }
176                 } else if (wait) {
177                         for (WebOfTrustUpdateJob updateJob : updateJobs) {
178                                 if (updateJob.equals(addContextJob)) {
179                                         return updateJob.waitForCompletion();
180                                 }
181                         }
182                 }
183                 return false;
184         }
185
186         /**
187          * Removes the given context from the given own identity.
188          *
189          * @param ownIdentity
190          *            The own identity to remove the context from
191          * @param context
192          *            The context to remove
193          */
194         public void removeContext(OwnIdentity ownIdentity, String context) {
195                 RemoveContextJob removeContextJob = new RemoveContextJob(ownIdentity, context);
196                 if (!updateJobs.contains(removeContextJob)) {
197                         logger.log(Level.FINER, "Adding Context Job: " + removeContextJob);
198                         try {
199                                 updateJobs.put(removeContextJob);
200                         } catch (InterruptedException ie1) {
201                                 /* the queue is unbounded so it should never block. */
202                         }
203                 }
204         }
205
206         /**
207          * Sets a property on the given own identity.
208          *
209          * @param ownIdentity
210          *            The own identity to set the property on
211          * @param propertyName
212          *            The name of the property to set
213          * @param propertyValue
214          *            The value of the property to set
215          */
216         public void setProperty(OwnIdentity ownIdentity, String propertyName, String propertyValue) {
217                 SetPropertyJob setPropertyJob = new SetPropertyJob(ownIdentity, propertyName, propertyValue);
218                 if (updateJobs.contains(setPropertyJob)) {
219                         updateJobs.remove(setPropertyJob);
220                 }
221                 logger.log(Level.FINER, "Adding Property Job: " + setPropertyJob);
222                 try {
223                         updateJobs.put(setPropertyJob);
224                 } catch (InterruptedException e) {
225                         /* the queue is unbounded so it should never block. */
226                 }
227         }
228
229         /**
230          * Removes a property from the given own identity.
231          *
232          * @param ownIdentity
233          *            The own identity to remove the property from
234          * @param propertyName
235          *            The name of the property to remove
236          */
237         public void removeProperty(OwnIdentity ownIdentity, String propertyName) {
238                 setProperty(ownIdentity, propertyName, null);
239         }
240
241         //
242         // SERVICE METHODS
243         //
244
245         /**
246          * {@inheritDoc}
247          */
248         @Override
249         protected void serviceRun() {
250                 while (!shouldStop()) {
251                         try {
252                                 WebOfTrustUpdateJob updateJob = updateJobs.take();
253                                 if (shouldStop() || (updateJob == stopJob)) {
254                                         break;
255                                 }
256                                 logger.log(Level.FINE, "Running Trust Update Job: " + updateJob);
257                                 long startTime = System.currentTimeMillis();
258                                 updateJob.run();
259                                 long endTime = System.currentTimeMillis();
260                                 logger.log(Level.FINE, "Trust Update Job finished, took " + (endTime - startTime) + " ms.");
261                         } catch (InterruptedException ie1) {
262                                 /* happens, ignore, loop. */
263                         }
264                 }
265         }
266
267         /**
268          * {@inheritDoc}
269          */
270         @Override
271         protected void serviceStop() {
272                 try {
273                         updateJobs.put(stopJob);
274                 } catch (InterruptedException ie1) {
275                         /* the queue is unbounded so it should never block. */
276                 }
277         }
278
279         /**
280          * Base class for WebOfTrust update jobs.
281          *
282          * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
283          */
284         private class WebOfTrustUpdateJob {
285
286                 /** Object for synchronization. */
287                 @SuppressWarnings("hiding")
288                 private final Object syncObject = new Object();
289
290                 /** Whether the job has finished. */
291                 private boolean finished;
292
293                 /** Whether the job was successful. */
294                 private boolean success;
295
296                 //
297                 // ACTIONS
298                 //
299
300                 /**
301                  * Performs the actual update operation.
302                  * <p>
303                  * The implementation of this class does nothing.
304                  */
305                 public void run() {
306                         /* does nothing. */
307                 }
308
309                 /**
310                  * Waits for completion of this job or stopping of the WebOfTrust
311                  * updater.
312                  *
313                  * @return {@code true} if this job finished successfully, {@code false}
314                  *         otherwise
315                  *
316                  * @see WebOfTrustUpdater#stop()
317                  */
318                 @SuppressWarnings("synthetic-access")
319                 public boolean waitForCompletion() {
320                         synchronized (syncObject) {
321                                 while (!finished && !shouldStop()) {
322                                         try {
323                                                 syncObject.wait();
324                                         } catch (InterruptedException ie1) {
325                                                 /* we’re looping, ignore. */
326                                         }
327                                 }
328                                 return success;
329                         }
330                 }
331
332                 //
333                 // PROTECTED METHODS
334                 //
335
336                 /**
337                  * Signals that this job has finished.
338                  *
339                  * @param success
340                  *            {@code true} if this job finished successfully,
341                  *            {@code false} otherwise
342                  */
343                 protected void finish(boolean success) {
344                         synchronized (syncObject) {
345                                 finished = true;
346                                 this.success = success;
347                                 syncObject.notifyAll();
348                         }
349                 }
350
351         }
352
353         /**
354          * Base class for WebOfTrust trust update jobs.
355          *
356          * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
357          */
358         private class WebOfTrustTrustUpdateJob extends WebOfTrustUpdateJob {
359
360                 /** The identity giving the trust. */
361                 protected final OwnIdentity truster;
362
363                 /** The identity receiving the trust. */
364                 protected final Identity trustee;
365
366                 /**
367                  * Creates a new trust update job.
368                  *
369                  * @param truster
370                  *            The identity giving the trust
371                  * @param trustee
372                  *            The identity receiving the trust
373                  */
374                 @SuppressWarnings("synthetic-access")
375                 public WebOfTrustTrustUpdateJob(OwnIdentity truster, Identity trustee) {
376                         this.truster = truster;
377                         this.trustee = trustee;
378                 }
379
380                 //
381                 // OBJECT METHODS
382                 //
383
384                 /**
385                  * {@inheritDoc}
386                  */
387                 @Override
388                 public boolean equals(Object object) {
389                         if ((object == null) || !object.getClass().equals(getClass())) {
390                                 return false;
391                         }
392                         WebOfTrustTrustUpdateJob updateJob = (WebOfTrustTrustUpdateJob) object;
393                         return ((truster == null) ? (updateJob.truster == null) : updateJob.truster.equals(truster)) && ((trustee == null) ? (updateJob.trustee == null) : updateJob.trustee.equals(trustee));
394                 }
395
396                 /**
397                  * {@inheritDoc}
398                  */
399                 @Override
400                 public int hashCode() {
401                         return getClass().hashCode() ^ ((truster == null) ? 0 : truster.hashCode()) ^ ((trustee == null) ? 0 : trustee.hashCode());
402                 }
403
404                 /**
405                  * {@inheritDoc}
406                  */
407                 @Override
408                 public String toString() {
409                         return String.format("%s[truster=%s,trustee=%s]", getClass().getSimpleName(), (truster == null) ? null : truster.getId(), (trustee == null) ? null : trustee.getId());
410                 }
411
412         }
413
414         /**
415          * Update job that sets the trust relation between two identities.
416          *
417          * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
418          */
419         private class SetTrustJob extends WebOfTrustTrustUpdateJob {
420
421                 /** The score of the relation. */
422                 private final Integer score;
423
424                 /** The comment of the relation. */
425                 private final String comment;
426
427                 /**
428                  * Creates a new set trust job.
429                  *
430                  * @param truster
431                  *            The identity giving the trust
432                  * @param trustee
433                  *            The identity receiving the trust
434                  * @param score
435                  *            The score of the trust (from -100 to 100, may be
436                  *            {@code null} to remote the trust relation completely)
437                  * @param comment
438                  *            The comment of the trust relation
439                  */
440                 public SetTrustJob(OwnIdentity truster, Identity trustee, Integer score, String comment) {
441                         super(truster, trustee);
442                         this.score = score;
443                         this.comment = comment;
444                 }
445
446                 /**
447                  * {@inheritDoc}
448                  */
449                 @Override
450                 @SuppressWarnings("synthetic-access")
451                 public void run() {
452                         try {
453                                 if (score != null) {
454                                         if (trustee instanceof DefaultIdentity) {
455                                                 ((DefaultIdentity) trustee).setTrust(truster, new Trust(score, null, 0));
456                                         }
457                                         webOfTrustConnector.setTrust(truster, trustee, score, comment);
458                                 } else {
459                                         if (trustee instanceof DefaultIdentity) {
460                                                 ((DefaultIdentity) trustee).setTrust(truster, null);
461                                         }
462                                         webOfTrustConnector.removeTrust(truster, trustee);
463                                 }
464                                 finish(true);
465                         } catch (WebOfTrustException wote1) {
466                                 logger.log(Level.WARNING, "Could not set Trust value for " + truster + " -> " + trustee + " to " + score + " (" + comment + ")!", wote1);
467                                 finish(false);
468                         }
469                 }
470
471         }
472
473         /**
474          * Update job that retrieves the trust relation between two identities.
475          *
476          * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
477          */
478         private class GetTrustJob extends WebOfTrustTrustUpdateJob {
479
480                 /**
481                  * Creates a new trust update job.
482                  *
483                  * @param truster
484                  *            The identity giving the trust
485                  * @param trustee
486                  *            The identity receiving the trust
487                  */
488                 public GetTrustJob(OwnIdentity truster, Identity trustee) {
489                         super(truster, trustee);
490                 }
491
492                 /**
493                  * {@inheritDoc}
494                  */
495                 @Override
496                 @SuppressWarnings("synthetic-access")
497                 public void run() {
498                         try {
499                                 Trust trust = webOfTrustConnector.getTrust(truster, trustee);
500                                 if (trustee instanceof DefaultIdentity) {
501                                         ((DefaultIdentity) trustee).setTrust(truster, trust);
502                                 }
503                                 finish(true);
504                         } catch (PluginException pe1) {
505                                 logger.log(Level.WARNING, "Could not get Trust value for " + truster + " -> " + trustee + "!", pe1);
506                                 finish(false);
507                         }
508                 }
509
510         }
511
512         /**
513          * Base class for context updates of an {@link OwnIdentity}.
514          *
515          * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
516          */
517         private class WebOfTrustContextUpdateJob extends WebOfTrustUpdateJob {
518
519                 /** The own identity whose contexts to manage. */
520                 protected final OwnIdentity ownIdentity;
521
522                 /** The context to update. */
523                 protected final String context;
524
525                 /**
526                  * Creates a new context update job.
527                  *
528                  * @param ownIdentity
529                  *            The own identity to update
530                  * @param context
531                  *            The context to update
532                  */
533                 @SuppressWarnings("synthetic-access")
534                 public WebOfTrustContextUpdateJob(OwnIdentity ownIdentity, String context) {
535                         Validation.begin().isNotNull("OwnIdentity", ownIdentity).isNotNull("Context", context).check();
536                         this.ownIdentity = ownIdentity;
537                         this.context = context;
538                 }
539
540                 //
541                 // OBJECT METHODS
542                 //
543
544                 /**
545                  * {@inheritDoc}
546                  */
547                 @Override
548                 public boolean equals(Object object) {
549                         if ((object == null) || !object.getClass().equals(getClass())) {
550                                 return false;
551                         }
552                         WebOfTrustContextUpdateJob updateJob = (WebOfTrustContextUpdateJob) object;
553                         return updateJob.ownIdentity.equals(ownIdentity) && updateJob.context.equals(context);
554                 }
555
556                 /**
557                  * {@inheritDoc}
558                  */
559                 @Override
560                 public int hashCode() {
561                         return getClass().hashCode() ^ ownIdentity.hashCode() ^ context.hashCode();
562                 }
563
564                 /**
565                  * {@inheritDoc}
566                  */
567                 @Override
568                 public String toString() {
569                         return String.format("%s[ownIdentity=%s,context=%s]", getClass().getSimpleName(), ownIdentity, context);
570                 }
571
572         }
573
574         /**
575          * Job that adds a context to an {@link OwnIdentity}.
576          *
577          * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
578          */
579         private class AddContextJob extends WebOfTrustContextUpdateJob {
580
581                 /**
582                  * Creates a new add-context job.
583                  *
584                  * @param ownIdentity
585                  *            The own identity whose contexts to manage
586                  * @param context
587                  *            The context to add
588                  */
589                 public AddContextJob(OwnIdentity ownIdentity, String context) {
590                         super(ownIdentity, context);
591                 }
592
593                 /**
594                  * {@inheritDoc}
595                  */
596                 @Override
597                 @SuppressWarnings("synthetic-access")
598                 public void run() {
599                         try {
600                                 webOfTrustConnector.addContext(ownIdentity, context);
601                                 ownIdentity.addContext(context);
602                                 finish(true);
603                         } catch (PluginException pe1) {
604                                 logger.log(Level.WARNING, String.format("Could not add Context “%2$s” to Own Identity %1$s!", ownIdentity, context), pe1);
605                                 finish(false);
606                         }
607                 }
608
609         }
610
611         /**
612          * Job that removes a context from an {@link OwnIdentity}.
613          *
614          * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
615          */
616         private class RemoveContextJob extends WebOfTrustContextUpdateJob {
617
618                 /**
619                  * Creates a new remove-context job.
620                  *
621                  * @param ownIdentity
622                  *            The own identity whose contexts to manage
623                  * @param context
624                  *            The context to remove
625                  */
626                 public RemoveContextJob(OwnIdentity ownIdentity, String context) {
627                         super(ownIdentity, context);
628                 }
629
630                 /**
631                  * {@inheritDoc}
632                  */
633                 @Override
634                 @SuppressWarnings("synthetic-access")
635                 public void run() {
636                         try {
637                                 webOfTrustConnector.removeContext(ownIdentity, context);
638                                 ownIdentity.removeContext(context);
639                                 finish(true);
640                         } catch (PluginException pe1) {
641                                 logger.log(Level.WARNING, String.format("Could not remove Context “%2$s” to Own Identity %1$s!", ownIdentity, context), pe1);
642                                 finish(false);
643                         }
644                 }
645
646         }
647
648         /**
649          * Base class for update jobs that deal with properties.
650          *
651          * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
652          */
653         private class WebOfTrustPropertyUpdateJob extends WebOfTrustUpdateJob {
654
655                 /** The own identity to update properties on. */
656                 protected final OwnIdentity ownIdentity;
657
658                 /** The name of the property to update. */
659                 protected final String propertyName;
660
661                 /**
662                  * Creates a new property update job.
663                  *
664                  * @param ownIdentity
665                  *            The own identity to update the property on
666                  * @param propertyName
667                  *            The name of the property to update
668                  */
669                 @SuppressWarnings("synthetic-access")
670                 public WebOfTrustPropertyUpdateJob(OwnIdentity ownIdentity, String propertyName) {
671                         this.ownIdentity = ownIdentity;
672                         this.propertyName = propertyName;
673                 }
674
675                 //
676                 // OBJECT METHODS
677                 //
678
679                 /**
680                  * {@inheritDoc}
681                  */
682                 @Override
683                 public boolean equals(Object object) {
684                         if ((object == null) || !object.getClass().equals(getClass())) {
685                                 return false;
686                         }
687                         WebOfTrustPropertyUpdateJob updateJob = (WebOfTrustPropertyUpdateJob) object;
688                         return updateJob.ownIdentity.equals(ownIdentity) && updateJob.propertyName.equals(propertyName);
689                 }
690
691                 /**
692                  * {@inheritDoc}
693                  */
694                 @Override
695                 public int hashCode() {
696                         return getClass().hashCode() ^ ownIdentity.hashCode() ^ propertyName.hashCode();
697                 }
698
699                 /**
700                  * {@inheritDoc}
701                  */
702                 @Override
703                 public String toString() {
704                         return String.format("%s[ownIdentity=%s,propertyName=%s]", getClass().getSimpleName(), ownIdentity, propertyName);
705                 }
706
707         }
708
709         /**
710          * WebOfTrust update job that sets a property on an {@link OwnIdentity}.
711          *
712          * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
713          */
714         private class SetPropertyJob extends WebOfTrustPropertyUpdateJob {
715
716                 /** The value of the property to set. */
717                 private final String propertyValue;
718
719                 /**
720                  * Creates a new set-property job.
721                  *
722                  * @param ownIdentity
723                  *            The own identity to set the property on
724                  * @param propertyName
725                  *            The name of the property to set
726                  * @param propertyValue
727                  *            The value of the property to set
728                  */
729                 public SetPropertyJob(OwnIdentity ownIdentity, String propertyName, String propertyValue) {
730                         super(ownIdentity, propertyName);
731                         this.propertyValue = propertyValue;
732                 }
733
734                 /**
735                  * {@inheritDoc}
736                  */
737                 @Override
738                 @SuppressWarnings("synthetic-access")
739                 public void run() {
740                         try {
741                                 if (propertyValue == null) {
742                                         webOfTrustConnector.removeProperty(ownIdentity, propertyName);
743                                         ownIdentity.removeProperty(propertyName);
744                                 } else {
745                                         webOfTrustConnector.setProperty(ownIdentity, propertyName, propertyValue);
746                                         ownIdentity.setProperty(propertyName, propertyValue);
747                                 }
748                                 finish(true);
749                         } catch (PluginException pe1) {
750                                 logger.log(Level.WARNING, String.format("Could not set Property “%2$s” to “%3$s” on Own Identity %1$s!", ownIdentity, propertyName, propertyValue), pe1);
751                                 finish(false);
752                         }
753                 }
754
755         }
756
757 }