/**
  * Si $resursive es true, se cargan las asociaciones de la clase y tambien se pasan a json.
  */
 public static function toJSON(PersistentObject $po, $recursive = false, $loopDetection = NULL, $currentPath = '')
 {
     if ($loopDetection === NULL) {
         $loopDetection = new ArrayObject();
     }
     // Necesito hacer que cada nodo tenga una path para poder expresar las referencias por loops detectados.
     // La idea es simple: (ver http://goessner.net/articles/JsonPath/)
     // - vacio es la path que referencia al nodo raiz
     // - el nombre del atributo referencia a un objeto hasOne relacionado
     // - el nombre del atributo con notacion de array referencia a un objeto hasMany relacionado
     // p.e. x.store.book[0].title donde x es el objeto raiz, entonces una path valida es: .store.book[0].title
     $loopDetection[$currentPath] = $po->getClass() . '_' . $po->getId();
     // Marca como recorrido: TODO falta la marca de loop cuando se detecta.
     $json = "{";
     $i = 0;
     $n = count($po->getAttributeTypes()) - 1;
     foreach ($po->getAttributeTypes() as $attr => $type) {
         $value = $po->aGet($attr);
         if (is_bool($value)) {
             $value ? $value = 'true' : ($value = 'false');
         }
         // Si no esta esto, aparece 1 para true y nada para false.
         $json .= '"' . $attr . '" : "' . $value . '"';
         // TODO: si es numero, no poner comillas
         if ($i < $n) {
             $json .= ", ";
         }
         $i++;
     }
     // Agrega errores de validacion si los hay
     // http://code.google.com/p/yupp/issues/detail?id=86
     if ($po->getErrors()->hasErrors()) {
         $errors = $po->getErrors();
         $json .= ', "errors": {';
         foreach ($errors as $attr => $theErrors) {
             $json .= '"' . $attr . '": [';
             foreach ($theErrors as $theError) {
                 $json .= '"' . $theError . '", ';
             }
             $json = substr($json, 0, -2);
             // Saco ', ' del final
             $json .= '], ';
         }
         $json = substr($json, 0, -2);
         // Saco ', ' del final
         $json .= '}';
     }
     if ($recursive) {
         foreach ($po->getHasOne() as $attr => $clazz) {
             $relObj = $po->aGet($attr);
             if ($relObj !== NULL) {
                 if (!in_array($relObj->getClass() . '_' . $relObj->getId(), (array) $loopDetection)) {
                     // FIXME: las tags de los atributos hijos de la instancia raiz deberian
                     //        tener su nombre igual al nombre del atributo, no el nombre de
                     //        la clase. Con este codigo es el nombre de la clase.
                     $json .= ', "' . $attr . '": ' . self::toJSON($relObj, $recursive, $loopDetection, $currentPath . '.' . $attr);
                 } else {
                     // Agrego un objeto referencia
                     $keys = array_keys((array) $loopDetection, $relObj->getClass() . '_' . $relObj->getId());
                     $path = $keys[0];
                     $json .= ', "' . $attr . '": "' . $path . '"';
                 }
             }
         }
         foreach ($po->getHasMany() as $attr => $clazz) {
             // TODO: type de la coleccion, en el de XML tengo:
             //$hm_node->setAttribute( 'type', $obj->getHasManyType($attr) ); // list, colection, set
             //$hm_node->setAttribute( 'of', $obj->getAttributeType($attr) ); // clase de las instancias que contiene la coleccion
             $relObjs = $po->aGet($attr);
             if (count($relObjs) > 0) {
                 $json .= ', "' . $attr . '": ';
                 $json .= self::toJSONArray($relObjs, $recursive, $attr, $loopDetection, $currentPath);
                 /*
                 $json .= ', "'. $attr .'": [';
                 
                 echo "attr hasMany $attr<br/>";
                 
                 $idx = 0; // Se usa para la referencia por loop en la JSON path
                 foreach ($relObjs as $relObj)
                 {
                    echo 'relObj: '. $relObj->getClass().'_'.$relObj->getId() .'<br/>';
                  
                    if(!in_array($relObj->getClass().'_'.$relObj->getId(), (array)$loopDetection)) // si no esta marcado como recorrido
                    {
                       $json .= self::toJSON( $relObj, $recursive, $loopDetection, $currentPath.'.'.$attr.'['.$idx.']' ) .', ';
                    }
                    else // referencia por loop
                    {
                       // Agrego un objeto referencia
                       $keys = array_keys((array)$loopDetection, $relObj->getClass().'_'.$relObj->getId());
                       $path = $keys[0];
                       $json .= '"'.$attr.'": "'. $path .'", ';
                    }
                    
                    $idx++;
                 }
                 
                 $json = substr($json, 0, -2); // Saco ', ' del final
                 $json .= ']';
                 */
             }
         }
     }
     $json .= '}';
     return $json;
 }
 /**
  * Agrega una instancia de PO a la coleccion de una relacion hasMany.
  * La operacion se hace en memoria, no guarda en la base de datos.
  */
 public function aAddTo($attribute, PersistentObject $value)
 {
     Logger::getInstance()->po_log("PO:aAddTo {$attribute} []=" . $value->getClass());
     // CHEK: attribute es un atributo hasMany
     // Si el rol tiene el nombre de la assoc declarado, necesito ver cual es el nombre
     // completo de la key en hasOne o hasMany porque usa attribute__assocName.
     $attribute = $this->getRoleWithAssocName($attribute);
     // TODO: Se podria poner la restriccion de que no se puede hacer set('id', xxx);
     // o sea el id no se puede modificar por el usuario.
     // (asi puedo asumir que si no tiene id es xq no esta guardado... y me ahorro consultar si existe en la base)
     // Aqui se hace todo lo del codigo comentado abajo
     $this->lazyLoadHasMany($attribute);
     // Chekeo de tipos con el tipo definido en hasMany para este atributo.
     // Si es colection, se agrega normalmente,
     // si es set se verifica que no hay otro con el mismo id,
     // si es list al salvar y cargar se respeta el orden en el que se agregaron los elementos.
     $add = false;
     switch ($this->hasManyType[$attribute]) {
         case self::HASMANY_COLLECTION:
         case self::HASMANY_LIST:
             // Por ahora hace lo mismo que COLECTION, en PM se verificaria el orden.
             $add = true;
             break;
         case self::HASMANY_SET:
             // Buscar repetidos por id, si ya esta no agrego de nuevo.
             $found = false;
             reset($this->attributeValues[$attribute]);
             $elem = current($this->attributeValues[$attribute]);
             while ($elem) {
                 if ($elem->getId() === $value->getId()) {
                     $found = true;
                     break;
                     // while
                 }
                 $elem = next($this->attributeValues[$attribute]);
             }
             $add = !$found;
             // Agrega solo si no esta.
             break;
     }
     if ($add) {
         $this->attributeValues[$attribute][] = $value;
         // TODO: Verificar que args0 es un PersistentObject y es simple!
         // FIXME: bool is_subclass_of ( mixed $object, string $class_name )
         $this->dirtyMany = true;
         // Marca como editado el hasMany
     }
 }