/**
  * 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;
 }
 /**
  * Nombre de la tabla que almacena informacion sobre la relacion entre 2 instancias de PO.
  * El nombre de la tabla de la relacion es el nombre derivado de la instancia1 concatenado
  * al nombre del atributo que apunta a la instancia 2, concatenado al nombre derivador de
  * la instancia 2.
  * @param PersistentObject $ins1 Instancia duenia de la asociacion.
  * @param String Atributo de $ins1 que apunta a $ins2.
  * @param PersistentObject $ins2 Instancia hija en la asociacion.
  */
 public static function relTableName(PersistentObject $ins1, $inst1Attr, PersistentObject $ins2)
 {
     // Problema: si el atributo pertenece a C1, y $ins1 es instancia de G1,
     //           la tabla que se genera para hasMany a UnaClase es "gs_unaclase_"
     //           y deberia ser "cs_unaclase_", esto es un problema porque si cargo
     //           una instancia de C1 no tiene acceso a sus hasMany "UnaClase".
     // TODO: esta es una solucion rapida al problema, hay que mejorarla.
     // Esta solucion intenta buscar cual es la clase en la que se declara el atributo hasMany
     // para el que se quiere generar la tabla intermedia de referencias, si no la encuentra,
     // es que el atributo hasMany se declaro en $ins1.
     // Tambien hay un problema cuando hay composite>
     // Si ins1->hasMany[ins1Attr] es a una superclase de ins2, genera mal el nombre de la tabla de join.
     // El nombre se tiene que generar a la clase para la que se declara le hasMany,
     // no para el nombre de tabla de ins2 (porque ins2 puede guardarse en otra tabla
     // que no sea la que se guarda su superclase a la cual fue declarado el hasMany
     // ins1->hasMany[inst1Attr]).
     // Solucion: ver si la clase a la que se declara el hasMany no es la clase de ins2,
     //           y verificar si ins2 se guarda en otra tabla que la clase a la que se
     //           declara el hasMany en ins1. Si es distinta, el nombre debe apuntar al
     //           de la clase declarada en el hasMany. (aunque en ambos casos es a esto,
     //           asi que no es necesario verificar).
     $classes = ModelUtils::getAllAncestorsOf($ins1->getClass());
     //Logger::struct( $classes, "Superclases de " . $ins1->getClass() );
     $instConElAtributoHasMany = $ins1;
     // En ppio pienso que la instancia es la que tiene el atributo masMany.
     foreach ($classes as $aclass) {
         $ins = new $aclass(NULL, true);
         //if ( $ins->hasManyOfThis( $ins2->getClass() ) ) // la clase no es la que tenga el atributo, debe ser en la que se declara el atributo
         if ($ins->attributeDeclaredOnThisClass($inst1Attr)) {
             //Logger::getInstance()->log("TIENE MANY DE " . $ins2->getClass());
             $instConElAtributoHasMany = $ins;
             break;
         }
         //Logger::struct( $ins, "Instancia de $aclass" );
     }
     $tableName1 = self::tableName($instConElAtributoHasMany);
     //echo "=== "  .  $ins1->getAttributeType( $inst1Attr ) . " ==== <br/>";
     // La tabla de join considera la tabla en la que se guardan las instancias del tipo
     // declarado en el hasMany, NO A LOS DE SUS SUBCLASES!!! (como podia ser ins2)
     $tableName2 = self::tableName($ins1->getAttributeType($inst1Attr));
     // $tableName2 = self::tableName( $ins2 );
     // TODO: Normalizar $inst1Attr ?
     //      echo "Nombre tabla relTableName: ". $tableName1 . "_" . $inst1Attr . "_" . $tableName2 ."<br/>";
     return $tableName1 . "_" . $inst1Attr . "_" . $tableName2;
     // owner_child
 }
 /**
  * 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
     }
 }